A. 问题描述
给定一个整数数组,元素的值有正有负。定义“连续子数组和”为连续几个数组的元素的和,求最大的连续子数组和。已知这个值在int能够表示的范围内。
B. 暴力做
- 无脑暴力做就是枚举所有的子数组,O(n^2),然后对于每个子数组求和,自然就找出最大的了,复杂度总共是O(n^3)。
- 能否优化?想想哪里做了重复多余的事情了?
- 没错,就是对每个子数组求和的时候,先做一下前缀和+后缀和的预处理,就可以将子数组求和的复杂度将为O(1)了,总的复杂度为O(n^2)。
具体实现代码如下:
int MaxSubArraySum(const vector<int>& v) {
int size = v.size();
vector<int> forward(size+1), backward(size+1);
// 计算前缀和
for (int i = 0; i < size; ++i)
forward[i+1] = forward[i] + v[i];
// 计算后缀和
for (int i = size-1; i >= 0; --i)
backward[i] = backward[i+1] + v[i];
// 获取整个数组所有元素的和
int AllSum = 0;
for (int i = 0; i < size; ++i)
AllSum += v[i];
int maxSum = AllSum;
// 枚举所有子数组,并求连续最大子数组和
for (int i = 0; i < size; ++i) {
for (int j = i; j < size; ++j)
maxSum = max(maxSum, AllSum - forward[i] - backward[j+1]);
}
return maxSum;
}
还有更快更好的方法吗?
C. 更好的方法
其实这是一道很简单的dp问题,有O(n)的做法的。
定义dp[i]为以第i位元素结尾的所有子数组中的和最大的那个和的值,那么转移方程很显然是:
dp[i] = max(dp[i-1] + X[i], X[i])
它的意思是,我自己这一位(即第i位)要和前面的凑到一起成为一组呢,还是我自己一个人一组呢?看谁大咯~写起来就几行而已:
int MaxSubArraySum2(const vector<int>& v) {
int size = v.size(), maxSum = v[0];
vector<int> dp(size);
dp[0] = v[0];
for (int i = 1; i < size; ++i) {
dp[i] = max(dp[i-1] + v[i], v[i]);
maxSum = max(maxSum, dp[i]);
}
return maxSum;
}
D. 总结
笨方法不一定是容易的,就像B和C的对比一样,2333,多接触一些好玩的算法,既可以丰富知识,也可以灵活自己的头脑,何乐不为?
最后附上自己此次的测试代码:
#include <stdio.h>
#include <ctime>
#include <cstdlib>
#include <vector>
#include <assert.h>
using namespace std;
vector<int> gen(int size) {
int MAX = 100000;
vector<int> v(size);
for (int i = 0; i < size; ++i) {
int key = rand() % MAX;
v[i] = (key > MAX/2) ? (key) : (-key);
}
return v;
}
void display(const vector<int>& v) {
for (int i = 0; i < v.size(); ++i)
printf("%d ", v[i]);
printf("\n");
}
// 暴力+前后缀和优化
int MaxSubArraySum(const vector<int>& v) {
int size = v.size();
vector<int> forward(size+1), backward(size+1);
// 计算前缀和
for (int i = 0; i < size; ++i)
forward[i+1] = forward[i] + v[i];
// 计算后缀和
for (int i = size-1; i >= 0; --i)
backward[i] = backward[i+1] + v[i];
// 获取整个数组所有元素的和
int AllSum = 0;
for (int i = 0; i < size; ++i)
AllSum += v[i];
int maxSum = AllSum;
// 枚举所有子数组,并求连续最大子数组和
for (int i = 0; i < size; ++i) {
for (int j = i; j < size; ++j)
maxSum = max(maxSum, AllSum - forward[i] - backward[j+1]);
}
return maxSum;
}
// 动态规划求解
int MaxSubArraySum2(const vector<int>& v) {
int size = v.size(), maxSum = v[0];
vector<int> dp(size);
dp[0] = v[0];
for (int i = 1; i < size; ++i) {
dp[i] = max(dp[i-1] + v[i], v[i]);
maxSum = max(maxSum, dp[i]);
}
return maxSum;
}
int main() {
srand(time(0));
for (int i = 0; i < 1000; ++i) {
int size = rand() % 997;
vector<int> v = gen(size);
if (MaxSubArraySum(v) != MaxSubArraySum2(v)) {
display(v);
printf("Your algorithm didn't work at this test cast...\n");
exit(0);
}
printf("Pass test case %d\n", i+1);
}
return 0;
}