给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
PS:刚好就在前两天学弟问了我一道题目,题意相同,贴图如下:
枚举所有可能的结果,并两两比较,得出最大值。
时间复杂度O(n^2),空间复杂度O(1)。
int maxSubArray(int* nums, int numsSize)
{
int curSum;
int maxSum = nums[0];
for(int i = 0; i < numsSize; ++i)
{
curSum = nums[i];
if(curSum > maxSum)
maxSum = curSum;
for(int j = i + 1; j < numsSize; ++j)
{
curSum += nums[j];
if(curSum > maxSum)
maxSum = curSum;
}
}
return maxSum;
}
我们先看两个引理:
引理1:
以负数开头的子序列不会是最大的子序列。
证明:令子序列为 {ai, …,aj},其中开头的元素 ai < 0,则 ai + … + aj < ai+1 + … + aj 显然成立。
引理2:
对子序列 {ai, …, aj},如果该子序列满足以下两个条件:
① 对 x 取 [i, j) 中的任意整数(包含 i,不包含 j),sum{ai, …, ax} > 0;
② sum{ai, …, aj} < 0,
则以该子序列中的任何元素 ap 开头的以 aj 为终结的任意子序列的和必定小于0,即对 p 取 [i, j) 中的任意整数(包含 i,不包含 j),sum{ap, …, aj} < 0。
证明(反证法):假设 sum{ap, …, aj} > 0,p 取 [i, j) 之间的整数,由引理2条件② sum{ai, …, aj} < 0 得出 sum{ai, …, ap-1} < 0,该结论违反了引理2中的条件① 对 x 取 [i, j) 中的任意整数(包含 i,不包含 j),sum{ai, …, ax} > 0。得证。
结论:
由引理1可知,若 a[i] < 0, 则应跳到 a[i + 1] 作为子序列的开头元素(如果 a[i + 1] > 0)。
由引理2可知,若 a[i] + … + a[j] < 0 且满足引理2的条件①,则应以 a[j + 1] 作为最大连续子序列的开头元素(如果 a[j + 1] > 0)。
根据以上结论写代码即可求解出最大连续子序列。
我们写出如下算法:
时间复杂度O(n),空间复杂度O(1)。
int maxSubArray(int* nums, int numsSize)
{
int curSum = nums[0];
int maxSum = nums[0];
for(int i = 1; i < numsSize; ++i)
{
if(curSum < 0)
curSum = 0;
curSum += nums[i];
if(curSum > maxSum)
maxSum = curSum;
}
return maxSum;
}
通过递归分治不断的缩小规模,问题结果就有三种,左边的解,右边的解,以及中间的解(有位置要求,从中介mid向两边延伸寻求最优解),得到三个解通过比较大小,得到最优解。
因为使用了二分,所以时间复杂度为O(nlogn);虽然使用了递归,但是在调用函数maxSubArrayPart和maxSubArrayAll时传递的是C语言指针nums,所以空间复杂度依然是O(1)。
这里我还发现了一个问题,使用 #define 宏定义会使执行时间增加很多,应该是宏展开占用了时间。
我们看看下面截图中的时间和空间大小,就可以发现了。
//左右两边合起来求解
int maxSubArrayAll(int* nums, int left, int mid, int right)
{
int leftCurSum = 0;
int leftMaxSum = nums[mid];
for(int i = mid; i >= left; i--)
{
leftCurSum += nums[i];
if(leftCurSum > leftMaxSum)
leftMaxSum = leftCurSum;
}
int rightCurSum = 0;
int rightMaxSum = nums[mid + 1];
for(int i = mid + 1; i <= right; i++)
{
rightCurSum += nums[i];
if(rightCurSum > rightMaxSum)
rightMaxSum = rightCurSum;
}
return leftMaxSum + rightMaxSum;
}
//递归
int maxSubArrayPart(int* nums, int left, int right)
{
if(left == right)
return nums[left];
int mid = (left + right) / 2;
int maxSum;
if(maxSubArrayPart(nums, left, mid) > maxSubArrayPart(nums, mid + 1, right))
maxSum = maxSubArrayPart(nums, left, mid);
else
maxSum = maxSubArrayPart(nums, mid + 1, right);
if(maxSum < maxSubArrayAll(nums, left, mid, right))
maxSum = maxSubArrayAll(nums, left, mid, right);
return maxSum;
}
int maxSubArray(int* nums, int numsSize)
{
return maxSubArrayPart(nums, 0, numsSize - 1);
}
//宏定义,求两数中较大的数
#define max(a,b) (a>b?a:b)
//左右两边合起来求解
int maxSubArrayAll(int* nums, int left, int mid, int right)
{
int leftCurSum = 0;
int leftMaxSum = nums[mid];
for(int i = mid; i >= left; i--)
{
leftCurSum += nums[i];
if(leftCurSum > leftMaxSum)
leftMaxSum = leftCurSum;
}
int rightCurSum = 0;
int rightMaxSum = nums[mid + 1];
for(int i = mid + 1; i <= right; i++)
{
rightCurSum += nums[i];
if(rightCurSum > rightMaxSum)
rightMaxSum = rightCurSum;
}
return leftMaxSum + rightMaxSum;
}
//递归
int maxSubArrayPart(int* nums, int left, int right)
{
if(left == right)
return nums[left];
int mid = (left + right) / 2;
return max(maxSubArrayPart(nums, left, mid),
max(maxSubArrayPart(nums, mid + 1, right),
maxSubArrayAll(nums, left, mid, right)));
}
int maxSubArray(int* nums, int numsSize)
{
return maxSubArrayPart(nums, 0, numsSize - 1);
}
//左右两边合起来求解
int maxSubArrayAll(int* nums, int left, int mid, int right)
{
int leftCurSum = 0;
int leftMaxSum = nums[mid];
for(int i = mid; i >= left; i--)
{
leftCurSum += nums[i];
if(leftCurSum > leftMaxSum)
leftMaxSum = leftCurSum;
}
int rightCurSum = 0;
int rightMaxSum = nums[mid + 1];
for(int i = mid + 1; i <= right; i++)
{
rightCurSum += nums[i];
if(rightCurSum > rightMaxSum)
rightMaxSum = rightCurSum;
}
return leftMaxSum + rightMaxSum;
}
//递归
int maxSubArrayPart(int* nums, int left, int right)
{
if(left == right)
return nums[left];
int mid = (left + right) / 2;
int maxSum;
maxSum = maxSubArrayPart(nums, left, mid) > maxSubArrayPart(nums, mid + 1, right) ?
maxSubArrayPart(nums, left, mid) : maxSubArrayPart(nums, mid + 1, right);
maxSum = maxSum > maxSubArrayAll(nums, left, mid, right) ?
maxSum : maxSubArrayAll(nums, left, mid, right);
return maxSum;
}
int maxSubArray(int* nums, int numsSize)
{
return maxSubArrayPart(nums, 0, numsSize - 1);
}
我们使用下面示例,对以上算法进行展开:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
到目前为止,我最满意的是思路二。