29. 最大连续子序列和(一)
一. 定义
1.序列:
给定一组数据,这组数据就叫做序列。这里的数据有可能是一年的交易额,或者有其余的含义。所以数据并不是经过排序的。比如 data = (1, 4, -3, 7, -6, 10).
2.连续子序列:
在序列中,任取连续区间的一组数据,叫做连续子序列。
3.最大连续子序列和:
把每个子序列看作一个单元,对其中的所有元素求和,获得的值就是一个子序列和。将所有子序列都求和,并从中选出一个最大的,这个值就是最大连续子序列和。
二.代码实现
1.实例分析
假设我们有一组数据,data = (1, 4, -3, 7, -6, 10),第一个子序列是(1) ,它的和就是 1 。第二个子序列是(1, 4),它的和是 5 。其余同理。
于是我们很快想出一个算法,用于计算最大连续子序列和,代码如下:
1 int max_sub_sum(const vector<int>& data) { 2 const int MIN = numeric_limits<int>::min(); 3 int result = MIN; 4 5 for (int range_ctrl = 0; range_ctrl < data.size(); range_ctrl++) { 6 for (int sub_end = range_ctrl; sub_end < data.size(); ++sub_end) { 7 int sub_sum = 0; 8 for (int sub_start = range_ctrl; sub_start <= sub_end; ++sub_start) { 9 sub_sum += data[sub_start]; 10 } 11 if (sub_sum > result) { 12 result = sub_sum; 13 } 14 } 15 } 16 17 return result; 18 }
2. 代码分析
显然,最外层循环用于控制大范围,让我们不至于跑到序列的外面。第二层和第三层循环应该看成一个整体,它们用于控制一个子序列的首尾下标。我们仔细观察发现,第二层循环控制子序列的结束下标,第三层循环控制子序列的开始下标。我们先卡住结束位置,再从头开始进行累加,一直加到结束位置(这个结束位置的元素是要计算的,这就是用了 ≤ 符号的原因)。不难看出,算法的时间复杂度是 O(n3),效率并不是很好。经过分析,我们得知,大部分的时间都用在了重复计算上,罪魁祸首就是第三层循环。那么我们来优化一下。
3. 优化
1 int max_sub_sum_2(const vector<int>& data) { 2 const int MIN = numeric_limits<int>::min(); 3 int result = MIN; 4 5 for (int sub_start = 0; sub_start < data.size(); ++sub_start) { 6 int sub_sum = 0; 7 for (int sub_end = sub_start; sub_end < data.size(); ++sub_end) { 8 sub_sum += data[sub_end]; 9 if (sub_sum > result) { 10 result = sub_sum; 11 } 12 } 13 } 14 15 return result; 16 }
现在只有两层循环。外层循环用于控制子序列的开头,内层循环用于控制子序列的结尾。外层循环的作用其实并没有改变,它仍然防止我们跑到序列的外边去。但是内层循环却不一样了,现在内层循环实际上充当了指定子序列开始的角色,而子序列结尾用序列大小控制就可以。在内层循环中,我们每累加一个元素,产生的和就是一个子序列的和,然后再进行判断即可。这样时间复杂度就成了 O(n2)。