【DP】连续子数组的最大和

版权声明:本文为博主原创学习笔记,如需转载请注明来源。 https://blog.csdn.net/SHU15121856/article/details/82655707

面试题42:连续子数组的最大和

输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。

解法1

如果全部是非负数,那么全加起来就完事了。这个题的困难就在于中间的负数,负数同样能把左右的子数组连接起来,但是代价就是会损失一些加和:
这里写图片描述
如果去比较这个负数(或者连续一些负数)带来的损失,是不是比两个子数组中的某一个还大,是很麻烦的,而且存在很多重叠的问题。

可以从前到后正常扫描数组,然后把数值加上来,并维护一个当下发现的最大和,如果比最大和还大就更新最大和。

另外用一个变量维护当前扫描的子数组和,每次循环中检查一下,如果子数组和是负的,那后面再加什么变量都不如从此直接截断:
这里写图片描述
所以这种时候直接拿当前遍历到的值将它替换掉,表示子数组现在从这个位置开始检查。

当遍历到负数时,其实不影响当前扫描的子数组和,因为负数的后面可能又是柳暗花明,遇到一串正数,两个子数组通过一些无伤大雅的负数连接起来,产生最大和。

这里作者的实现方式我认为是一种”懒汉方式”,就是对遇到的负数不做特殊处理,一样加到当前和里,留到下次扫描时,检查现在子数组是不是被这个负数搞成负的(或者0)了,如果是的话,就放弃前面的所有,接着从当前遍历到的位置开始新的子数组(即前面分析的直接替换)。

#include<bits/stdc++.h>
using namespace std;

bool g_InvalidInput = false;//指示是否出错的全局变量 

//传入数组和数组长度,返回连续子数组的最大和 
int FindGreatestSumOfSubArray(int *pData, int nLength) {
    //输入合法性检查 
    if((pData == nullptr) || (nLength <= 0)) {
        g_InvalidInput = true;
        return 0;
    }

    //这句没意义,这里还是保留作者的写法 
    g_InvalidInput = false;

    int nCurSum = 0;//累加的子数组和 
    //最小int数,即-(2^31),该变量记录运行至此捕获的最大和 
    int nGreatestSum = 0x80000000;
    //遍历数组中的每个元素 
    for(int i = 0; i < nLength; ++i) {
        if(nCurSum <= 0)//如果之前累加的和是非正的 
            nCurSum = pData[i];//那抛弃之前的,用这次遇到的数开始累加 
        else//如果之前累加的和是正的,那还可能对后续有积极作用 
            nCurSum += pData[i];//那把这次的和加上来 

        if(nCurSum > nGreatestSum)//如果找到了更大的和 
            nGreatestSum = nCurSum;//替换掉 
    }

    return nGreatestSum;
}


int main() {
    int data[] = {1, -2, 3, 10, -4, 7, 2, -5};
    cout<<FindGreatestSumOfSubArray(data,sizeof(data)/sizeof(int));
    return 0;
}
解法2

使用动态规划,设以第i个数字结尾的子串的最大和是f[i]。那么当f[i-1]是非正的时没法给f[i]做贡献,此时f[i]就是用当前的pData[i];而当f[i-1]是正的时,连接上pData[i]就成为了f[i]了。

作者没提供代码,这个DP比较好写,自己实现了一下。

#include<bits/stdc++.h>
using namespace std;

bool g_InvalidInput = false;//指示是否出错的全局变量 

//传入数组和数组长度,返回连续子数组的最大和 
int FindGreatestSumOfSubArray(int *pData, int nLength) {
    //输入合法性检查 
    if((pData == nullptr) || (nLength <= 0)) {
        g_InvalidInput = true;
        return 0;
    }

    int f_i=0;//既是上次循环的f[i-1],也是这次循环的f[i]
    //f[i]的意思是以第i个数字结尾的子数组的最大和 

    int max_f_i=0x80000000;//最大的f[i]就是所求,不一定在哪结束 

    for(int i=0;i<nLength;i++){
        if(f_i<=0)//如果f[i-1]是负的,不能为f[i]做出贡献
            f_i=pData[i];//当前f[i]就从当前位置开始当前位置结束就是最大的 
        else//如果是正的,那么f[i]肯定要连接它的
            f_i=f_i+pData[i];//把它和当前这个结点连接起来就是f[i] 
        if(f_i>max_f_i)//如果找到了更大的f[i]
            max_f_i=f_i;
    }

    return max_f_i;//最大的f[i]就是所求 
}

int main() {
    int data[] = {1, -2, 3, 10, -4, 7, 2, -5};
    cout<<FindGreatestSumOfSubArray(data,sizeof(data)/sizeof(int));
    return 0;
}

猜你喜欢

转载自blog.csdn.net/SHU15121856/article/details/82655707