给定K个整数组成的序列{ N1, N2, ..., NK },“连续子列”被定义为{ Ni, Ni+1, ..., Nj },其中 1≤i≤j≤K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。
本题旨在测试各种不同的算法在各种数据情况下的表现。各组测试数据特点如下:
- 数据1:与样例等价,测试基本正确性;
- 数据2:102个随机整数;
- 数据3:103个随机整数;
- 数据4:104个随机整数;
- 数据5:105个随机整数;
输入格式:
输入第1行给出正整数K (≤100000);第2行给出K个整数,其间以空格分隔。
输出格式:
在一行中输出最大子列和。如果序列中所有整数皆为负数,则输出0。
输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20
先给出程序的输入输出部分框架
#include<iostream>
#include<algorithm>
#include<vector>
#include<climits>
int maxSubArray(std::vector<int>& nums);
int main()
{
int n, temp, res;
std::cin >> n;
std::vector<int> array;
for (int i = 0; i < n; i++) {
std::cin >> temp;
array.push_back(temp);
}
res = maxSubArray(array);
std::cout << res << std::endl;
}
最简单的一种实现是穷举所有子列和,然后求这些子列和的最大值,这个算法的时间复杂度是O(N^3)
int maxSubArray(std::vector<int>& nums)
{
int res = nums[0], sum = 0;
// i 表示子列的开头,j表示子列的结尾
for (int i = 0; i < nums.size(); i++) {
for (int j = i; j < nums.size(); j++) {
sum = 0;
for (int k = i; k <= j; k++) {
sum += nums[k];
}
if (sum > res)
res = sum;
}
}
return res;
}
上面的这种算法具有大量的重复计算,用dp(i,j)表示以i开头和j结尾的子列和,那么dp(i,j+1)=dp(i,j)+nums[j+1],因此可以记录下每次计算子列和的值,动态跟新,去掉在里面一层循环,现在的时间复杂是O(N^2)
int maxSubArray(std::vector<int>& nums)
{
int res = nums[0], sum;
// i 表示子列的开头,j表示子列的结尾
for (int i = 0; i < nums.size(); i++) {
sum = 0;
for (int j = i; j < nums.size(); j++) {
sum += nums[j];
if (sum > res)
res = sum;
}
}
return res;
}
这仍然不是一种最优的算法,下面令dp[i]表示以下标i结尾的最大子列和,容易发现具有以下关系dp[i]=max(dp[i - 1] + nums[i], nums[i]),所以可以在一次遍历中计算得到答案,时间复杂是O(N)
int maxSubArray(std::vector<int>& nums)
{
int ans = nums[0];
std::vector<int> dp(nums.size(),nums[0]);
for (int i = 1; i < nums.size(); i++) {
dp[i] = std::max(nums[i], dp[i - 1] + nums[i]);
ans = std::max(ans, dp[i]);
}
return ans;
}
这种解法比较优雅,但用到了O(N)的空间复杂度,事实上,在上一步递推的过程中,可以进一步优化,将空间复杂度降为O(1)
int maxSubArray(std::vector<int>& nums)
{
int ans = nums[0];
int LastSum = nums[0];
int NowSum;
for (int i = 1; i < nums.size(); i++) {
NowSum = std::max(nums[i], LastSum + nums[i]);
LastSum = NowSum;
ans = std::max(ans, NowSum);
}
return ans;
}
这个题目的一个进阶版本是要求输出最大子列和对应的开始位置和结束位置,这个需要在上面代码中修改得到,另外注意,vector数组使用的时候有一些坑,需要特别注意检查。
int maxSubArray(std::vector<int>& nums)
{
int ans = nums[0];
int LastSum = nums[0];
int NowSum;
int Start, End, ResStart, ResEnd;
Start = 0;
for (int i = 1; i < nums.size(); i++) {
if (LastSum >= 0) {
NowSum = LastSum + nums[i];
End = i;
}
else {
NowSum = nums[i];
Start = i;
End = i;
}
LastSum = NowSum;
if (NowSum > ans) {
ans = NowSum;
ResStart = Start;
ResEnd = End;
}
}
if(ans>=0)
std::cout << ans << " " << nums[ResStart] << " " << nums[ResEnd] << std::endl;
else
std::cout << 0 << " " << nums[0] << " " << nums[nums.size()-1] << std::endl;
return ans;
}