给出一个整数序列S,其中有N个数,定义其中一个非空连续子序列T中所有数的和为T的“序列和”。 对于S的所有非空连续子序列T,求最大的序列和。
至少有以下三种解法:
1. 动态规划
- 时间复杂度O(n)
void max_subarray_sum(const std::vector<int>& a, std::ostream& os)
{
int res = INT_MIN;
int cur_sum = 0;
for (int i = 0; i < a.size(); i++)
{
cur_sum = std::max(cur_sum + a[i], a[i]);
res = std::max(res, cur_sum);
}
os << res;
}
此种解法可以理解为,假设有最大序列和的子数组是以原数组中第i个元素结尾的,那么在遍历的过程中做两件事:
- 对于每个i,找到以第i个元素结尾的子数组的最大序列和
- 对于上述找到的最大序列和,比较得出其中最大的即为最终结果
代码中的cur_sum
就是为了做第1件事,而res
是为了做第2件事。得到cur_sum
的逻辑为,以第i个元素结尾的序列和最大的子数组,等于以第(i-1)个元素结尾的序列和最大的子数组加上第i个元素,或者等于第i个元素。
2. 扫描法
- 时间复杂度O(n)
void max_subarray_sum(const std::vector<int> &a, std::ostream &os)
{
int res = INT_MIN;
int cur_sum = INT_MIN;
for (int i = 0; i < a.size(); i++)
{
if (cur_sum < 0)
{
cur_sum = a[i];
}
else
{
cur_sum += a[i];
}
if(res < cur_sum)
{
res = cur_sum;
}
}
os << res;
}
这种扫描法其实和动态规划是一样的,因为,如果cur_sum < 0
那么cur_sum + a[i] < a[i]
否则cur_sum + a[i] >= a[i]
。同时,这也提供了另一种理解思路,在扫描的过程中逐步求序列和,当求得的和为负时,将原有的和丢弃,开始重新计算,因为负的和与后面的值相加,只能使后面的值减小。
3. 分治法
- 时间复杂度O(nlogn)
int max_subarray_sum_helper(const std::vector<int>& a, int left, int right)
{
if (abs(left - right) < 2)
{
return a[left] > a[right] ? a[left] : a[right];
}
int middle = (left + right) / 2;
int left_max = max_subarray_sum_helper(a, left, middle);
int right_max = max_subarray_sum_helper(a, middle + 1, right); // (middle + 1) instead of middle
LOG(WARNING) << left << " " << middle << " " << right;
int middle_max = INT_MIN;
int cur_sum = 0;
for (int i = middle; i >= left; i--)
{
cur_sum += a[i];
middle_max = cur_sum > middle_max ? cur_sum : middle_max;
}
cur_sum = middle_max; // cur_sum should be set to the left_max_sum
for (int i = middle + 1; i <= right; i++)
{
cur_sum += a[i];
middle_max = cur_sum > middle_max ? cur_sum : middle_max;
}
int maxsum = left_max > right_max ? left_max : right_max;
maxsum = maxsum > middle_max ? maxsum : middle_max;
LOG(INFO) << "left: " << left << " right: " << right << " max_sum: " << maxsum;
return maxsum;
}
void max_subarray_sum(const std::vector<int> &a, std::ostream &os)
{
os << max_subarray_sum_helper(a, 0, a.size() - 1);
}
这里的分治法的基本思想就是将数组分为两部分,最大序列和即为左边数组的最大序列和,或者右边数组的最大序列和,或者是位于中间跨越左右两个数组的最大序列和。
4.参考文献
[1] 求连续子数组的最大和
https://www.cnblogs.com/waytofall/archive/2012/04/10/2439820.html
[2] [LeetCode] Maximum Subarray 最大子数组
https://www.cnblogs.com/grandyang/p/4377150.html