题目
输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)
例如,输入的数组为 {1,-2, 3, 10,-4,7,2,-5 },和最大的子数组为 {3,10,-4,7,2 },因此输出为该子数组的和 18.
分析
看到这道题,很多人都能想到最直观的方法,即枚举出数组的所有子数组并求出它们的和。一个长度为n的数组 ,总共有n(n+1)/2 个子数组。计算出所有子数组的和,最快也要O(n^2)的时间。通常最直观的方法不会是最优的解法,面试官将提示我们还有更快的算法。
解法一:举例分析数组的规律
试着从头到尾逐个累加示例数组中的每个数字。初始化为0.第一步加上第一个数字1,此时和为1.接下来第二步加上数字-2,和就变成了-1.第三步加上数字3.我们注意到由于此前累计的和是-1,小于0,那如果用-1加上3,得到的和是2,比2本身还笑。也就是说从第一个数字开始的子数组的和会小于从第三个数字开始的子数组的和。因此我们不用考虑从第一个数字开始的子数组,之前累计的和也被抛弃。
整个过程如下:
步骤 | 操作 | 累加的子数组和 | 最大的子数组和 |
---|---|---|---|
1 | 加1 | 1 | 1 |
2 | 加-2 | -1 | 1 |
3 | 抛弃前面的和-1,加3 | 3 | 3 |
4 | 加10 | 13 | 13 |
5 | 加-4 | 9 | 13 |
6 | 加7 | 16 | 16 |
7 | 加2 | 18 | 18 |
8 | 加-5 | 13 | 18 |
bool g_InvalidInput = false;
int FindGreatestSumOfSubArray(int *pData, int nLength)
{
if((pData == NULL) || (nLength <= 0))
{
g_InvalidInput = true;
return 0;
}
g_InvalidInput = false;
int nCurSum = 0;
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;
}
我们需要考虑无效的输入,比如输入的数组参数为空指针、数组长度小于等于0等情况。因此需要定义一个全局遍历来标记是否输入无效。
解法二:应用动态规划法
如果算法的功底足够扎实,还可以用动态规划的思想来分析这个问题。如果用函数 f(i) 表示以第 i 个数字结尾的子数组的最大和 ,那么我们需要求出 max[f(i)]
,其中 0<=i<=n
。递归公式如下
此公式的意义:当以第i-1个数字结尾的子数组中所有数字的和小于0时,如果把这个负数与第 i 个数累加,得到的结果比第 i 个数字本身还要小,所以这种情况下 以第 i个数字结尾的子数组就是第 i 个数字本身(如表的第3步)。如果以第 i-1 个数字结尾的子数组中所有数字的和大于0,与第 i 个数字累加就得到以 第 i 个数字结尾的子数组中所有数字的和。
测试用例&代码
(1)功能测试(输入的数组中有正数也有负数,输入的数组中全是正数,输入的数组中全是负数 )
(2)特殊输入测试(表示数组的指针为NULL指针)
本题考点
(1)对时间复杂度的理解
(2)对动态规划的理解。如果应聘者熟练掌握了动态规划算法,那么他就能轻松地找到解题方案。如果没有想到用动态规划的思想,那么就需要仔细分析累加子数组的和的过程,从而找到解题规律。
(3)思维的全面性。能否合理地处理无效的输入,对面试结果有很重要的影响。