版权声明:本文为博主原创学习笔记,如需转载请注明来源。 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;
}