• 最大连续和问题


      最大连续和,也称最大连续子段和。这个问题虽然简单,但其中蕴含了很多算法的思想,包括动态规划和分治法。

    问题描述

    最大连续和问题  给出一个长度为n的序列A0,A1,...,An-1求最大连续和。也就是,要求找到一组(i,j)满足0≤i≤j≤n-1,使得Ai+Ai+1+...+Aj尽量大。

    解法1  暴力枚举

      我们可以枚举出所有的连续和,记B(i,j)=Ai+Ai+1+...+Aj,也就是 i 从0取到n-1,j 从 i 取到n-1。因此,我们要做的分为两部分,第一部分是枚举出所有的B(i,j),第二部分是对于具体的B(i,j)计算它的值,计算B(i,j)的值的时间花费为j-i+1,因此总的时间复杂度为

    (T(n)=sum_{i=0}^{n-1}sum_{j=i}^{n-1}(j-i+1)=frac{n(n+1)(n+2)}{6}=Theta(n^3))

      具体实现如下(python代码)

    def get_max_continue_sum(A):
        n=len(A)
        best=A[0]
        for i in xrange(n):
            for j in xrange(i,n):
                B_ij=sum(A[i:j])
                if best<B_ij:
                    best=B_ij
        return best
    

    一点改进方法

      这里包含一点动态规划的思想。

      计算连续和的时候,我们使用公式B(i,j)=Ai+Ai+1+...+Aj。也就是说,我们需要j-i+1步才能计算出B(i,j),但是这是没有必要的。

      设前缀和S(i)=A0+A1+...+Ai,那么有B(i,j)=S(j)-S(i-1)。有了这个式子,我们就可以通过前缀和计算连续和。改进后的算法的时间复杂度(T(n)=Theta(n^2))。

      具体代码如下

    def get_max_continue_sum(A):
        n=len(A)
        S=[A[0]]
        for i in xrange(1,n):
            Si=S[i-1]+A[i]
            S.append(Si)
    
        best=A[0]
        for i in xrange(n):
            for j in xrange(i,n):
                B_ij=S[j]-S[i-1]
                if best<B_ij:
                    best=B_ij
        return best
    

    解法2  分治法

      分治的英文是Divide and Conquer,也就是将问题划分为多个子问题,然后分别解决。

    思路  对于一个序列{A0,A1,...,An-1}中的一个元素Ak,假设最大字段和为B(i,j),那么存在两种情况,A[i:j]要么包括Ak,要么不包括Ak。如果A[i:j]要么包括Ak,那我们怎么求B(i,j)呢?B(i,j)=B(i,k-1)+Ak+B(k+1,j),这也就是说,通过一遍扫描,就求出包含元素k的最大字段和,时间花费为(O(n))(原本会花费(O(n^2))的时间,想一想为什么)。  

      于是,我们将一个序列划分为左半和右半两部分,分别求出相应的最大字段和B1和B2,同时求出起点位于左半、终点位于右半的最大连续和B3,那么该序列的最大字段和B=max(B1,B2,B3)。

      比如序列{-2,4,5,-1,-6,9,-1,3},我们将它划分为两部分{-2,4,5,-1}和{-6,9,-1,3},那么B1=4+5=9,B2=9-1+3=11,B3=(4+5-1)+(-6+9)=11,因此B=max(9,11,11)=11。

      要说明的是,在求B1和B2时,我们使用的方法是调用函数本身。因此,此方法的时间复杂度(T(n))满足(T(n)=T(n/2)+O(n)),解得(T(n)=nlog(n))。

      具体实现如下

    #python 2.7
    def get_max_continue_sum(A):
        n=len(A)
        k=n/2
        B1=get_max_continue_sum(A[:k])
        B2=get_max_continue_sum(A[k:])
        
        L_best=_get_best_of_left(A[:k])
        R_best=_get_best_of_right(A[k:])
        B3=L_best+R_best
        
        return max(B1,B2,B3)
        
    def _get_best_of_left(A):
        n=len(A)
        v=A[-1]
        best=v
        for i in xrange(n-2, 0 ,-1):
            v+=A[i]
            best=max(best,v)
        return best
    
    def _get_best_of_right(A):
        n=len(A)
        v=A[0]
        best=v
        for i in xrange(1, n):
            v+=A[i]
            best=max(best,v)
        return best
    

    解法3  动态规划法(1)

      这个方法是在解法1的改进的基础上,进行改进的。

    思路  由于有B(i,j)=S(j)-S(i-1)。假设B(i,j)是最大字段和,那么说明S(j)足够大,S(i-1)也足够小。

      于是,我们可以找出使得S(j)最大的j,和使得S(i-1)最小的i。但是并不能这么做,因为如果i>j那就尴尬了。正确的做法是,对于一个 j,在[0,j]的范围内寻找最小的S。

      具体实现如下

    def get_max_continue_sum(A):
        n=len(A)
        S=[A[0]]
        for i in xrange(1,n):
            Si=S[i-1]+A[i]
            S.append(Si)
    
        best=A[0]
        minS=A[0]
        for j in xrange(1,n):
            best=max(best,S[j]-minS)
            minS=min(minS,S[j])
        return best
    

      很容易得到,这种方法的时间复杂度是O(n)。

    解法4  动态规划法(2)

      我没有想到竟然还有一种时间复杂度为O(n)的算法,直到我看了这篇博客——最大连续区间和的算法总结

      我们定义maxn[i]为以i为结尾的最大连续和,则很容易找到递推关系:maxn[i]=max{0,maxn[i-1]}+a[i]。

      因此

    def get_max_continue_sum(A):
        n=len(A)
        best=A[0]
        maxn=A[0]
        for j in xrange(1,n):
            maxn=max(0,maxn)+a[j]
            best=max(best,maxn)        
        return best
  • 相关阅读:
    打印二叉树中节点的所有祖先
    1.把2叉查找树转换成双向链表
    Linux下tar.xz结尾的文件的解压方法
    Floyd算法
    c缺陷与陷阱笔记-第七章 可移植性代码
    c缺陷与陷阱笔记-第六章 预处理器
    c缺陷与陷阱笔记-第四章 连接
    C语言小程序(四)、杨辉三角
    C语言小程序(三)、判断两个日期之差
    C语言小程序(二)、计算第二天日期
  • 原文地址:https://www.cnblogs.com/super-zhang-828/p/6437749.html
Copyright © 2020-2023  润新知