python实现最大子序和(分治+动态规划)


Posted in Python onJuly 05, 2019

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

思路:

首先我们分析题目,我们思考,为什么最大和的连续子数组不包含其他的元素而是这几个呢?因为如果我们想在现有的基础上去扩展当前连续子数组,相邻的元素是一定要被加入的,而相邻元素中可能会减损当前的和。

思路一:

遍历法,On:

算法过程:遍历数组,用onesum去维护当前元素加起来的和。当onesum出现小于0的情况时,我们把它设为0。然后每次都更新全局最大值。

class Solution:
  def maxSubArray(self, nums):
    """
    :type nums: List[int]
    :rtype: int
    """
    #onesum维护当前的和
    onesum = 0
    maxsum = nums[0]
    for i in range(len(nums)):
      onesum += nums[i]
      maxsum = max(maxsum, onesum)
      #出现onesum<0的情况,就设为0,重新累积和
      if onesum < 0:
        onesum = 0
    return maxsum

算法证明:一开始思考数组是个空的,把我们每次选一个nums[i]加入onesum看成当前数组新增了一个元素,也就是用动态的眼光去思考。过程很简单,代码很短,但为什么这样就能达到效果呢?我们进行的加和是按顺序来的,从数组第一个开始加。

当我们的i选出来后,加入onesum。这时有2种情况

1)假设我们这个onesum一直大于0,从未被<0过。那也就是说我们计算的每一次的onesum都大于0,而每一次计算的onesum都是包括开头元素的一段子序列(尾部一直随i变化)。看似我们没有考虑所有可能序列,但实际上所有可能的序列都已经被考虑过了。这里简单讲一下,待会po原文。

   a)以当前子序列开头为开头,中间任一处结尾的序列。这种情况是一直在扫描的,也有一直保存更新,所以不用怕丢失信息。

   b)以当前子序列结尾为结尾,中间任一处开头的序列。这种情况一定的和小于以当前子序列开头为开头,结尾为结尾的序列。因为前面缺失的那一段经过我们的前提,它也是加和大于0的。

   c)以中间元素为开头和结尾的序列。和小于以当前子序列开头为开头,此分序列结尾为结尾的序列。因为前面缺失的那一段经过我们的前提,它也是加和大于0的。

2)出现小于0的情况,就说明我们当前形成的这个子序是第一次出现小于0的情况。现在至少我们要新形成的连续数组不能在整个的包括之前的连续子序了,因为我们在之前的那个连续子序里加出了<0的情况。但问题是我们需不需要保留一些呢?是不是所有以当前子序结尾为结尾的任意开头的子序都要被舍弃呢?答案是是的,因为那一段也一定小于0,因为那一段的加和会小于以当前子序开头为开头,当前子序结尾为结尾的序列(见前面证明)。于是抛弃掉它们,重新开始新的子序。

思路二:

动态规划 On

算法过程:

设sum[i]为以第i个元素结尾的最大的连续子数组的和。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i]= max(sum[i-1] + a[i], a[i])。可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小

class Solution:
 
 
  def maxSubArray(self, nums): 
    """ 
    :type nums: List[int] 
    :rtype: int 
    """ 
    length=len(nums) 
    for i in range(1,length): 
      #当前值的大小与前面的值之和比较,若当前值更大,则取当前值,舍弃前面的值之和 
      subMaxSum=max(nums[i]+nums[i-1],nums[i]) 
      nums[i]=subMaxSum#将当前和最大的赋给nums[i],新的nums存储的为和值 
    return max(nums)

算法证明:这道题的代码我直接使用了题目数据中的nums数组,因为只要遍历一遍。nums[i]表示的是以当前这第i号元素结尾(看清了一定要包含当前的这个i)的话,最大的值无非就是看以i-1结尾的最大和的子序能不能加上我这个nums[i],如果nums[i]>0的话,则加上。注意我代码中没有显式地去这样判断,不过我的Max表达的就是这个意思,然后我们把nums[i]确定下来。

思路三:

分治递归

算法过程:

分治法,最大子序和要么在左半部分,要么在右半部分,要么就横跨两部分(即包括左半部分的最后一个元素,和右半部分的第一个元素)。返回这三种情况的最大值即可。第三种情况,其中包括左半部分最后一个元素的情形,需要挨个往前遍历,更新最大值。包含右半部分的第一个元素的情况类似。总的时间复杂度O(nlogn)

class Solution(object):
  def maxSubArray(self, nums):
    #主函数
    left = 0
    #左右边界
    right = len(nums)-1
    #求最大和
    maxSum = self.divide(nums,left,right)
    return maxSum
    
  def divide(self,nums,left,right):
    #如果只有一个元素就返回
    if left==right:
      return nums[left]
    #确立中心点
    center = (left+right)//2
    #求子序在中心点左边的和
    leftMaxSum = self.divide(nums,left,center)
    #求子序在中心点右边的和
    rightMaxSum = self.divide(nums,center+1,right)
    
    #求子序横跨2边的和,分成左边界和和右边界和
    leftBorderSum = nums[center]
    leftSum = nums[center]
    for i in range(center-1,left-1,-1):
      leftSum += nums[i]
      if leftSum>leftBorderSum:
        #不断更新左区块的最大值
        leftBorderSum = leftSum
      
    rightBorderSum = nums[center+1]
    rightSum = nums[center+1]
    for i in range(center+2,right+1):
      rightSum += nums[i]
      if rightSum>rightBorderSum:
        #不断更新右区块的最大值
        rightBorderSum = rightSum
    #左边界的和 + 右边那块的和
    BorderSum = leftBorderSum + rightBorderSum
    return max(leftMaxSum,rightMaxSum,BorderSum)

算法证明:

总的来说还是超级巧妙的。不断的切不断的切数组,把一块数组看成左中右三个部分。实际上这有点像枚举,但我们在枚举时利用了二分的思路,优化了很多。所以枚举当然可以达到我们的目标了,因为我们不断在计算以一定包括中间节点的子序的最大和。

总结:

今天写了很多很多,都没时间复习了。。。但是收获还是很大的。比如动态规划,不一定一定要建立数组然后返回数组的最后一项,动态规划其实是很灵活的。但是动态规划的每一项代表的意义要想好。

分治递归,实际在帮我们算所有数组只不过用了二分的思路优化。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python迭代器的使用方法实例
Nov 21 Python
以Python的Pyspider为例剖析搜索引擎的网络爬虫实现方法
Mar 30 Python
Python爬取三国演义的实现方法
Sep 12 Python
python爬取w3shcool的JQuery课程并且保存到本地
Apr 06 Python
Python 判断是否为质数或素数的实例
Oct 30 Python
pycharm恢复默认设置或者是替换pycharm的解释器实例
Oct 29 Python
对python for 文件指定行读写操作详解
Dec 29 Python
Python中extend和append的区别讲解
Jan 24 Python
基于python3抓取pinpoint应用信息入库
Jan 08 Python
python列表切片和嵌套列表取值操作详解
Feb 27 Python
Python实现捕获异常发生的文件和具体行数
Apr 25 Python
Python叠加矩形框图层2种方法及效果
Jun 18 Python
Python实现最大子序和的方法示例
Jul 05 #Python
python gensim使用word2vec词向量处理中文语料的方法
Jul 05 #Python
python开发之anaconda以及win7下安装gensim的方法
Jul 05 #Python
python使用opencv对图像mask处理的方法
Jul 05 #Python
Python代码太长换行的实现
Jul 05 #Python
Python PyCharm如何进行断点调试
Jul 05 #Python
python 使用matplotlib 实现从文件中读取x,y坐标的可视化方法
Jul 04 #Python
You might like
php提取字符串中网站url地址的方法
2014/12/03 PHP
[原创]smarty简单模板变量输出方法
2016/07/09 PHP
Yii框架日志操作图文与实例详解
2019/09/09 PHP
php文件包含的几种方式总结
2019/09/19 PHP
PHP实现单条sql执行多个数据的insert语句方法
2019/10/11 PHP
IE8 兼容性问题(属性名区分大小写)
2009/06/04 Javascript
jQuery中获取Radio元素值的方法
2013/07/02 Javascript
jquery获取当前点击对象的value方法
2014/02/28 Javascript
jquery实现图片上传之前预览的方法
2015/07/11 Javascript
JavaScript操作 url 中 search 部分方法函数
2016/06/15 Javascript
基于jQuery和hwSlider实现内容左右滑动切换效果附源码下载(一)
2016/06/22 Javascript
Html5 js实现手风琴效果
2020/04/17 Javascript
基于JavaScript 性能优化技巧心得(分享)
2017/12/11 Javascript
JS小球抛物线轨迹运动的两种实现方法详解
2017/12/20 Javascript
ES6下子组件调用父组件的方法(推荐)
2018/02/23 Javascript
浅谈vue方法内的方法使用this的问题
2018/09/15 Javascript
JavaScript变量提升和严格模式实例分析
2019/01/27 Javascript
原生js实现瀑布流效果
2020/03/09 Javascript
使用Element的InfiniteScroll 无限滚动组件报错的解决
2020/07/27 Javascript
[02:33]DOTA2亚洲邀请赛趣味视频之吐真话筒
2018/03/31 DOTA
[03:10]超级美酒第四天 fy拉比克秀 大合集
2018/06/05 DOTA
Python实现繁?转为简体的方法示例
2018/12/18 Python
python 通过麦克风录音 生成wav文件的方法
2019/01/09 Python
python网络应用开发知识点浅析
2019/05/28 Python
Django CBV与FBV原理及实例详解
2019/08/12 Python
Python GUI库PyQt5样式QSS子控件介绍
2020/02/25 Python
Python dict的常用方法示例代码
2020/06/23 Python
python实现测试工具(二)——简单的ui测试工具
2020/10/19 Python
HTML5 LocalStorage 本地存储刷新值还在
2017/03/10 HTML / CSS
高校毕业生自我鉴定
2013/10/27 职场文书
师范学院教师自荐书
2014/01/31 职场文书
上班玩游戏检讨书
2014/02/07 职场文书
医院竞聘演讲稿
2014/05/16 职场文书
Go中的条件语句Switch示例详解
2021/08/23 Golang
Mysql数据库表中为什么有索引却没有提高查询速度
2022/02/24 MySQL
PHP正则表达式之RCEService回溯
2022/04/11 PHP