PAT甲级题库Debug日志(持续更新)

1007 Maximum Subsequence Sum

项目场景:

求最大子序列,打印最大子序列的和,以及首尾元素的下标。注意:

  1. 若最大子序列不唯一,则取首尾元素下标最小的最大子序列。
  2. 若最大子序列为空,则规定输出的和为0,下标为主序列首尾元素的下标。

问题1描述:

如何在线性时间内求解该问题?

解决方案:

设主序列存储在列表numbers中。采用Kadane算法,该算法采用动态规划的思想来求解问题,将问题划分成如下几步:

  1. 任取主序列中的一个元素,设下标为i,求以numbers[i]为子序列尾元素的最大子序列。
  2. 假设numbers[i]有前驱元素,并且已知以numbers[i-1]结尾的最大子序列,若在其基础上增加一项numbers[i]使得新序列的和大于0,那么最大子序列就是这一新序列;否则,最大子序列是空序列,序列和为0。
  3. 假设i=0,若numbers[0]>=0,则最大子序列是由numbers[0]构成的序列;否则,最大子序列为空。
  4. 以步骤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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值