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脚本来获取mp3文件tag信息的教程
May 04 Python
python字符串常用方法
Jun 14 Python
python实现画循环圆
Nov 23 Python
mac在matplotlib中显示中文的操作方法
Mar 06 Python
Python如何优雅删除字符列表空字符及None元素
Jun 25 Python
详解pyqt5的UI中嵌入matplotlib图形并实时刷新(挖坑和填坑)
Aug 07 Python
Python matplotlib模块及柱状图用法解析
Aug 10 Python
python实现取余操作的简单实例
Aug 16 Python
python中绕过反爬虫的方法总结
Nov 25 Python
python获取天气接口给指定微信好友发天气预报
Dec 28 Python
Python3利用scapy局域网实现自动多线程arp扫描功能
Jan 21 Python
Django开发RESTful API实现增删改查(入门级)
May 10 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 mysql数据库操作分页类
2008/06/04 PHP
自动完成JS类(纯JS, Ajax模式)
2009/03/12 Javascript
基于jquery的跟随屏幕滚动代码
2012/07/24 Javascript
jQuery Mobile页面跳转后未加载外部JS原因分析及解决
2013/03/18 Javascript
jquery实现弹出层登录和全屏层注册特效
2015/08/28 Javascript
详解JavaScript正则表达式之分组匹配及反向引用
2016/03/09 Javascript
js控制li的隐藏和显示实例代码
2016/10/15 Javascript
JS如何生成一个不重复的ID的函数
2016/12/25 Javascript
利用jquery正则表达式在页面验证url网址输入是否正确
2017/04/04 jQuery
js判断文件类型大小并给出提示的实现方法
2018/01/03 Javascript
JavaScript中var、let、const区别浅析
2018/06/24 Javascript
JavaScript函数节流和函数去抖知识点学习
2018/07/31 Javascript
vue-cli3脚手架的配置及使用教程
2018/08/28 Javascript
JavaScript函数、闭包、原型、面向对象学习笔记
2018/09/06 Javascript
json字符串对象转换代码实例
2019/09/28 Javascript
nodejs中使用archive压缩文件的实现代码
2019/11/26 NodeJs
vue中实现拖动调整左右两侧div的宽度的示例代码
2020/07/22 Javascript
python检查URL是否正常访问的小技巧
2017/02/25 Python
mysql 之通过配置文件链接数据库
2017/08/12 Python
python 的 scapy库,实现网卡收发包的例子
2019/07/23 Python
python pandas cumsum求累计次数的用法
2019/07/29 Python
Python实现多线程下载脚本的示例代码
2020/04/03 Python
Python3实现打印任意宽度的菱形代码
2020/04/12 Python
使用CSS3来实现滚动视差效果的教程
2015/08/24 HTML / CSS
HTML5的表单(绝对特别强大的功能)使用示例
2013/06/20 HTML / CSS
纪伊国屋泰国网上书店:Kinokuniya泰国
2017/12/24 全球购物
牦牛毛户外探险服装:Kora
2019/02/08 全球购物
商务英语专业自荐信
2013/10/14 职场文书
和睦家庭事迹
2014/05/14 职场文书
村党支部群众路线教育实践活动对照检查材料
2014/09/26 职场文书
四风问题对照检查材料整改措施
2014/09/27 职场文书
学校党委干部个人对照检查材料思想汇报
2014/10/09 职场文书
云台山导游词
2015/02/03 职场文书
2015年医务科工作总结范文
2015/05/26 职场文书
辩论赛开场白大全(主持人+辩手)
2015/05/29 职场文书
优化Mysql查询的示例
2022/04/26 MySQL