算法导论—最大子数组问题

华电北风吹
天津大学认知计算与应用重点实验室
日期:2015/6/30

问题描述:
比如你获得了一个投资某个股票的机会,并且,你已经准确知道了将来几天这一只股票的相对于前一天的差值,比如为[13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7] (原始价格为[100,113,110,85,105,102,86,63,81,101,94,106,101,79,94,90,97])那么就有一个问题,从那一天买入,哪一天卖出获益最大?这里就是一个最大子数组问题。
最大子数组问题:在一个数组中找出最大的非空连续子数组

求解方法一:
暴力求解找出所有的组合,共有C(n,2)种选择,时间复杂度 Θ(n2)

求解方法二:
分治法求解最大子数组问题,计算复杂度 Θ(nlogn)
分治法求解最大字数组的思想是把每一个数组一分为二,每次考虑最大字数组所在的三种可能情况:跨中点,中点左侧,中点右侧。算法的计算量主要在于跨中点这种情况,中点单侧主要是控制划分深度,所以每一层计算复杂度是 Θ(n) ,二分以后深度为 logn ,因此分治法的计算复杂度是 Θ(nlogn)
代码如下

def MaxCrossSubArray(A,low,mid,high):
    LeftMaxSum=A[mid]
    leftSum=A[mid]
    leftIndex=mid
    for i in range(mid-1,low-1,-1):
        leftSum=leftSum+A[i]
        if leftSum>LeftMaxSum:
            LeftMaxSum=leftSum
            leftIndex=i
    rightMaxSum=0
    rightSum=0
    rightIndex=mid
    for i in range(mid+1,high+1):
        rightSum+=A[i]
        if rightSum>rightMaxSum:
            rightMaxSum=rightSum
            rightIndex=i
    MaxSum=LeftMaxSum+rightMaxSum
    return (MaxSum,leftIndex,rightIndex)

def MaxSubArray(A,low,high):
    if low==high:
        return (A[low],low,high)
    mid=(low+high)//2
    Left=MaxSubArray(A,low,mid)
    Cross=MaxCrossSubArray(A,low,mid,high)
    Right=MaxSubArray(A,mid+1,high)
    List=[Left,Cross,Right]
    result=sorted(List,key = lambda list : list[0],reverse=True)
    return result[0]

a=[13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7]
print(MaxSubArray(a,0,len(a)-1))

代码最后返回一个tuple,tuple包含三个元素,第一个值最大字数组的值,后两个分开始开始索引和终止索引。
这个代码写完,调试成功后,有些地方自己还不是特别明白。在这儿写下来,从新整理一下思路,加深一下对递归的理解。
这个分治想法特别简单,如果不考虑返回最大字数组索引的话特别好理解,就是每一次迭代我都递归寻找单侧最大的,然后与当前跨中点的比较,返回大的。
但是如果我想新加一个返回索引的功能时,可以看到,我的返回索引的地方只有两个,是跨中点的那个函数和递归函数里面递归到字数组只有一个元素的时候。刚开始我的疑惑在于,如果只设置这一个返回索引的地方,是不是有问题呀?我的主要疑惑在于,如果出现连接怎么办,这里是我多想了,因为在每一层的每一个分支,都要运行跨中点的代码,逐层向上的跨中点代码,就是为了包括下层的连接情况。或者说这里的三种情况的划分已经把所有的情况都包括了。

求解方法三:
动态规划解法。
在这部分描述两个等价的方法对应于两种数据输入格式。
如果输入数据就是股票价格——思路是从前往后扫描数组,记录并更新扫描过程中的最小值,对扫描到的每个值与当前扫描到的最小值做差值,将差值最大值输出。代码见如下int maxProfit(vector& prices)方法。
如果就是求解最大子数组问题——也是对数组进行扫描,不过相比于上面的保存扫描过程中的最小值,这里对扫描到的每个元素进行叠加,如果叠加结果为负,将和归零(对应于上面算法的更新最小值过程),从新叠加,保存叠加到的最大值输出。代码见int maxSubArray();
代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices)
    {
        int len = prices.size();
        if (len < 2)
            return 0;
        int cur = prices[0];
        int result = 0;
        for (int i = 1; i < len; i++)
        {
            cur = min(cur, prices[i]);
            result = max(result, prices[i] - cur);
        }
        return result;
    }

    int maxSubArray(const vector<int> arr)
    {
        int maxSum = 0;
        int currSum = 0;
        for (int i = 0; i < arr.size(); i++)
        {
            currSum += arr[i];
            if (currSum > maxSum)
                maxSum = currSum;
            if (currSum < 0)
                currSum = 0;
        }
        return maxSum;
    }
    int maxSubArray(const vector<int> arr, int &begin, int & end)
    {
        int maxSum = 0;
        int currSum = 0;
        int newbegin = 0;
        begin = 0;
        for (int i = 0; i < arr.size(); i++)
        {
            currSum += arr[i];
            if (currSum > maxSum)
            {
                maxSum = currSum;
                begin = newbegin;
                end = i;
            }
            if (currSum < 0)
            {
                currSum = 0;
                newbegin = i + 1;
            }
        }
        return maxSum;
    }
};

最大连续子数组问题有个歧义的地方,就是如果全部是负数的话,子数组取空还是取最大值的问题。这里附上另一篇博客。

剑指offer—连续子数组的最大和

你可能感兴趣的:(算法导论)