同样:博客的主要目的在于记录求解时的思路。
题目:给定一个整数数组,请找出一个连续子数组,使得该子数组的和最大。输出答案时,请分别返回第一个数字和最后一个数字的下标。(如果两个相同的答案,请返回其中任意一个)
方法1:穷举法,计算出任意一个数的所有子数组的和,并找出最大的,同时记录起始和结束index.
下面根据样例来说明, 给定 [-3, 1, 3, -3, 4],根据穷举法可以得到如下的表格:
第i行j列(j > i ) 表示 数组中i ~j之间数的和。通过表格可以直接找到最大的子数组和并确定起始位置(图中的第二列第五行,对应数组中[1,4])。
该方案代码如下:
class Solution {
public:
/*
* @param A: An integer array
* @return: A list of integers includes the index of the first number and the index of the last number
*/
vector<int> continuousSubarraySum(vector<int> &A) {
// write your code here
// solution 1
int maxSum = -1000;
int indexStart = 0;
int indexEnd = 0;
for ( int i = 0; i < A.size(); i++ )
{
int j = i;
int tmpSum = 0;
for ( ; j < A.size(); j++ )
{
tmpSum += A[j];
if ( tmpSum > maxSum )
{
maxSum = tmpSum;
indexStart = i;
indexEnd = j;
}
}
}
vector<int> vecRet;
vecRet.push_back(indexStart);
vecRet.push_back(indexEnd);
return vecRet;
}
};
时间复杂度 T = n + (n-1) + …1 ⇒ O(N^2) ;空间复杂度 O(1)。
该代码在lintCode上运行之后,会发生超时现象,原因就在于时间复杂度太高。那么可以用什么方案进行优化呢。通过观察上面的表格,很容易发现后面的数据被加了很多次,另外可以看到,每一列最后一个数等于当列第一个数和下一列的最后一个数的和,即以i开始子数组和Sum(i)等于 A[i] + Sum(i+1) ==> Sum(i) = A[i] + Sum(i+1)。
那么,要使Sum(i) 最大该如何做呢,其实就很简单了。
SumMax(i) = A[i] + Sum(i+1) > 0 ? Sum(i+1) : 0;
从动态规划里看,这应该属于此问题的最优子结构了,边界就是 Sum(A.size() -1) = A[A.size()-1]了。计算出每一个数开始的最大子数组和,然后找出最大的就可以得到结果了。根据边界来看,显然从后往前计算,时间上更优,还是上面的例子,从后往前计算,可以得到如下的最大字数和的数组。
箭头表示计算方向,找到最大的数所在的index,即为起始index,往后直到子数组和小于0为止,即为终止index.
代码如下:
class Solution {
public:
/*
* @param A: An integer array
* @return: A list of integers includes the index of the first number and the index of the last number
*/
vector<int> continuousSubarraySum(vector<int> &A) {
// write your code here
// solution 2
vector<int> partSum;
partSum.resize(A.size(), 0);
int nLen = A.size();
int maxSum = -10000;
int indexStart = 0;
int indexEnd = nLen - 1;
int indexTmp = nLen - 1;
for ( int j = nLen - 1; j >= 0; j-- )
{
if ( j == nLen - 1 )
{
partSum[j] = A[j];
if ( partSum[j] > maxSum )
{
indexStart = j;
maxSum = partSum[j];
}
continue;
}
if ( partSum[j+1] >= 0 )
{
partSum[j] = partSum[j+1] + A[j];
}
else
{
partSum[j] = A[j];
indexTmp = j; //重置临时的终点位置
}
if ( partSum[j] > maxSum )
{
indexStart = j;
maxSum = partSum[j];
indexEnd = indexTmp; // 临时终点位置生效
}
}
vector<int> ret;
ret.push_back(indexStart);
ret.push_back(indexEnd);
return ret;
}
};
上述方案,仅需遍历一遍数组即可求得最大的连续子数组和。时间复杂度O(N),空间复杂度O(N)。但也很容易的看出,空间复杂度可以降为O(1)。
总结:此题的关键在于找出子数组和与数组之间的关系。很多时候一眼想不出最佳的方案时,不妨先把暴力解法写出来,然后看看是否能优化。