1007 Maximum Subsequence Sum
项目场景:
求最大子序列,打印最大子序列的和,以及首尾元素的下标。注意:
- 若最大子序列不唯一,则取首尾元素下标最小的最大子序列。
- 若最大子序列为空,则规定输出的和为0,下标为主序列首尾元素的下标。
问题1描述:
如何在线性时间内求解该问题?
解决方案:
设主序列存储在列表numbers中。采用Kadane算法,该算法采用动态规划的思想来求解问题,将问题划分成如下几步:
- 任取主序列中的一个元素,设下标为i,求以numbers[i]为子序列尾元素的最大子序列。
- 假设numbers[i]有前驱元素,并且已知以numbers[i-1]结尾的最大子序列,若在其基础上增加一项numbers[i]使得新序列的和大于0,那么最大子序列就是这一新序列;否则,最大子序列是空序列,序列和为0。
- 假设i=0,若numbers[0]>=0,则最大子序列是由numbers[0]构成的序列;否则,最大子序列为空。
- 以步骤3为起点,用步骤2可以推导出任意元素结尾的子序列,从中选取最大子序列。
用伪代码表示如下。
void max_subarray(List numbers, int &best_sum) {
best_sum = 0;
cur_sum = 0;
for (i = 0; i < ListLength(numbers); i++) {
cur_sum = max(cur_sum + numbers[i], 0);
best_sum = max(best_sum, cur_sum);
}
}
问题2描述:
如何求最大子序列的首元素?
解决方案:
假设已知numbers[i-1](0 < i < n - 1,线性表从0开始计数)结尾的最大子序列的首元素下标为cur_head,和为cur_sum,求numbers[i]结尾的最大子序列首元素。当numbers[i] + cur_sum >= 0时,如果numbers[i-1]结尾的最大子序列不为空序列,那么numbers[i]结尾的最大子序列首元素下标仍为cur_head;如果numbers[i-1]结尾的最大子序列为空序列,要使得numbers[i]结尾的最大子序列首元素下标仍为cur_head,cur_head应指向numbers[i]。当numbers[i] + cur_sum < 0时,numbers[i]结尾的最大子序列为空,如前面的第二种情况所述,为了使numbers[i + 1]的最大子序列首元素下标直接取cur_head,我们应该将cur_head的值更新为i + 1。
经过上面的分析,要计算以每个元素结尾的最大子序列的首元素,我们应该在之前伪代码开头声明一个变量cur_head = 0,并在循环中加入如下语句。
if (cur_sum + numbers[i] < 0) {
cur_head = i + 1;
}
问题3描述:
何时更新当前情况下最大子序列的首尾元素下标(注意,这个最大子序列是和为best_sum的子序列,而不是和为cur_sum的子序列)?
解决方案:
当已有的best_sum < cur_sum时,最大子序列显然需要更新为与cur_sum对应的子序列。其中,新的首元素就是cur_head,而尾元素就是i。
假设cur_sum对应的子序列不为空,当已有的best_sum == cur_sum时,如果best_sum对应的子序列不为空,那么就不需要更新它的首尾元素下标,因为题目要求如果最大子序列不唯一,那么取首尾元素下标最小的最大子序列。由于我们是从左往右寻找最大子序列,因此先找到的子序列一定首尾元素下标最小。当best_sum对应的子序列为空时,说明此时还没找到一个最大子序列,所以best_sum对应的子序列需要更新为cur_sum对应的子序列。
其他情况best_sum对应的子序列都不需要更新。
综上可知,只有比cur_sum小或最大子序列还未找到时,best_sum对应的子序列才需要更新,有没有办法将这两种情况统一起来?事实上,只有当cur_sum的旧值与numbers[i]相加小于0时,以numbers[i]结尾的最大子序列才为空序列,其他情况(包括我们前面提到的两种情况)下该子序列都不为空。而best_sum对应的子序列为空只有一开始还没找到一个最大子序列时才会出现,所以我们可以将best_sum的初值置为-1,更新的判断条件统一为best_sum < cur_sum,一旦找到了某个以numbers[i]结尾的子序列不为空,最大子序列和以及首尾元素下标就会更新,并且此后不会再出现第二种情况。注意,如果始终未找到一个非空最大子序列,程序最后要将best_sum恢复为0。可以输出最大子序列首尾元素的改进算法如下。
void max_subarray_advanced( List numbers,
int & best_sum,
int & best_head_val,
int & best_tail_val)
{
cur_sum = 0, best_sum = -1;
cur_head = 0;
for (i = 0;, i < ListLength(numbers); i++) {
cur_sum += numbers[i];
if (cur_sum + numbers[i] < 0) {
cur_head = i + 1;
cur_sum = 0;
}
else if (cur_sum > best_sum) {
best_sum = cur_sum;
best_head_val = numbers[cur_head];
best_tail_val = numbers[i];
}
}
if (best_sum < 0) best_sum = 0;
}