PTA | 1007 Maximum Subsequence Sum
1007 Maximum Subsequence Sum
作者 CHEN, Yue
单位 浙江大学
Given a sequence of K integers { N1, N2, ..., NK }. A continuous subsequence is defined to be { Ni, Ni+1, ..., Nj } where 1≤i≤j≤K. The Maximum Subsequence is the continuous subsequence which has the largest sum of its elements. For example, given sequence { -2, 11, -4, 13, -5, -2 }, its maximum subsequence is { 11, -4, 13 } with the largest sum being 20.
Now you are supposed to find the largest sum, together with the first and the last numbers of the maximum subsequence.
Input Specification:
Each input file contains one test case. Each case occupies two lines. The first line contains a positive integer K (≤10000). The second line contains K numbers, separated by a space.
Output Specification:
For each test case, output in one line the largest sum, together with the first and the last numbers of the maximum subsequence. The numbers must be separated by one space, but there must be no extra space at the end of a line. In case that the maximum subsequence is not unique, output the one with the smallest indices i and j (as shown by the sample case). If all the K numbers are negative, then its maximum sum is defined to be 0, and you are supposed to output the first and the last numbers of the whole sequence.
Sample Input:
10
-10 1 2 3 4 -5 -23 3 7 -21
Sample Output:
10 1 4
万事开头难,先读题!
给定K个整数{ N1,N2,...,NK }。连续子序列被定义为{ Ni,Ni+1,...,其中1≤i≤j≤K。最大子序列(Maximum Subsequence)是一个连续的子序列,它具有最大的元素和。例如,给定序列{-2,11,-4,13,-5,-2 },其最大子序列为{ 11,-4,13 },最大和为20。
现在你应该找到最大的总和,以及最大子序列的第一个和最后一个数字。
输入规范:
每个输入文件包含一个测试用例。每种情况占两行。第一行包含正整数K(≤10000)。第二行包含K个数字,由空格分隔。
输出规格:
对于每个测试用例,在一行中输出最大和,以及最大子序列的第一个和最后一个数字。数字之间必须用一个空格隔开,但行尾不能有多余的空格。如果最大子序列不是唯一的,则输出具有最小索引i和j的子序列(如示例所示)。如果所有的K数都是负数,那么它的最大和被定义为0,你应该输出整个序列的第一个和最后一个数字。
由题目可以提取到以下关键信息:
1, 给出指定长度的数组,一行输入长度,一行输入数组,最大长度是10000
2, 要求的结果是:最大连续子数组的和以及这段子数组的起始位置和末尾位置,考虑是差分和前缀和的应用
3, 如果是全负数数组,需要按照规定的输出规范进行输出
题目分析完之后,现在是手搓代码时间!!!
本题要求很简洁明了,即求最大的连续子数组和,在这里考虑使用差分与前缀和的思想对本题进行求解,同时又需要输出这段子数组的起始位置和末尾位置,因此首先定义两个变量用于记录这两个位置:head和tail,对输入数据进行处理,定义k用于接收数组的长度,对于C++,可以考虑定义长度为10001的数组用于存储,python的列表没有长度限制,因此只需要定于列表生成式对输入进行处理即可。
由于考虑使用差分和前缀和的思想解决本题,因此这里为了方便后续操作,分别对输入数组的下标0位置和前缀和数组的下标为零位置赋初值:0,使得前缀和数组中自下标位置起为“前 [i] 个子数组的和”,至于输入数组赋初值的原因,则是为了配合前缀和数组方便操作。
初始化代码和对于输入的处理部分如下:
python部分:
k = int(input()) # 接收输入
lst = [int(i) for i in input().split()] # 接收输入数组
lst.insert(0,0) # 输入数组下标为0位置赋初值0
sum_ = [0] * (len(lst)+1) # 生成前缀和数组,为了防止索引越界,一般考虑稍微初始化大一点点
for i in range(1, len(lst)):sum_[i] = sum_[i-1] + lst[i] # 计算前缀和数组
C++部分:
cin >> k;// 接收输入sum_[0] = 0; // 前缀和数组初始值赋0for(int i=1;i<=k; i++){cin>> arr[i]; // 接收输入数组}for(int i=1; i<= k; i++){sum_[i] = sum_[i-1] + arr[i]; // 计算前缀和数组}
接下来是本题解题的核心部分,根据前缀和数组计算连续数组的最大和部分。
由于初始化前缀和数组第一个元素为0,这里则直接从下标为1的位置开始操作,首先定义用于记录最大和的变量result,初始化为-1,这里因为全负数数组有规定的输出格式,且不需要其他额外的操作,因此为了遍历完前缀和数组后可以分辨出该数组是否为全负数数组,此处初始化为-1而不是0,因为全负数数组其子序列的和不可能为正数,但非全负数数组可能存在全零的情况,故此处不初始化为0,避免对后续造成干扰,细节处应该给予充分考虑,避免造成不必要的代码调试时间的浪费。
此后,我们使用循环,遍历前缀和数组,初始化一个标记变量lowest,用于记录每次最长子数组发生变化后的起始部分,这里初始化为0,因为0下标对应的元素初始化为0,不纳入考虑,循环遍历时,应当使用前缀和数组的元素对下标为lowest的前缀和数组元素进行相减,比如:对于下标为i的前缀和数组sum[i],与标记位置元素sum[lowest] ,其差值为自下标lowest+1到下标为i这段子数组的和,得到这部分子数组的和,我们可以将其与result进行比较,只要发现结果大于result,则更新result,同时head更新为lowest+1,tail更新为i,即【head , tail】部分即为当前所遍历得到的最大连续子数组和,通过循环不断更新i的位置,最终将得到最大的连续子数组和result,以及其初始位置和结束位置下标,同时需要注意的是:标记lowest并不是一成不变的,需要在每次遍历的时候比较sum[i]以及sum[lowest]的大小,只要sum[i]比sum[lowest]小,则更新lowest的位置为i,这里也不难理解,当被减数sum[i]一定时,只有减数sum[lowest]最小,得到的差才能最大,也就是下标为lowest+1到i这部分子数组的和才能最大,这也是解决连续子数组问题的关键点之一。以下是这部分的代码实现:
python:
res = -1 # 最大连续子数组和
lowest = 0 # 标记位置
head = 0
tail = 0
for end in range(len(lst)):if sum_[end] - sum_[lowest] > res: # 当前计算结果大于之前计算得到的最大连续子数组和,更新res = sum_[end] - sum_[lowest]head = lowest + 1tail = endif sum_[lowest] > sum_[end]: # 保证减数最小lowest = end
C++:
// 初始化对应变量
int lowest=0,result=-1;
int head,tail;for(int end=1; end<=k; end ++){if(sum_[end] - sum_[lowest] > result){ // 当前计算得到的最大连续子数组和大于以前计算的result = sum_[end] - sum_[lowest];head = arr[lowest + 1];tail = arr[end];}if(sum_[lowest] > sum_[end]){ // 保证减数最小lowest = end;}
}
最后是完整的的代码部分:
C++:
#include<bits/stdc++.h>
using namespace std;int k;
int lowest=0,result=-1;
int head,tail;
int arr[1001],sum_[1001];int main(){cin >> k;// 接收输入sum_[0] = 0; // 前缀和数组初始值赋0for(int i=1;i<=k; i++){cin>> arr[i]; // 接收输入数组}for(int i=1; i<= k; i++){sum_[i] = sum_[i-1] + arr[i]; // 计算前缀和数组}for(int end=1; end<=k; end ++){if(sum_[end] - sum_[lowest] > result){result = sum_[end] - sum_[lowest];head = arr[lowest + 1];tail = arr[end];}if(sum_[lowest] > sum_[end]){lowest = end;}}if(result < 0){cout << 0 << " " << arr[1] << " " << arr[k];}else{cout << result << " " << head <<" " << tail;}
}
python:
k = int(input()) # 接收输入
lst = [int(i) for i in input().split()] # 接收输入数组
lst.insert(0,0) # 输入数组下标为0位置赋初值0
sum_ = [0] * (len(lst)+1) # 生成前缀和数组,为了防止索引越界,一般考虑稍微初始化大一点点
for i in range(1, len(lst)):sum_[i] = sum_[i-1] + lst[i] # 计算前缀和数组
res = -1
lowest = 0
head = 0
tail = 0
for end in range(len(lst)):if sum_[end] - sum_[lowest] > res:res = sum_[end] - sum_[lowest]head = lowest + 1tail = endif sum_[lowest] > sum_[end]:lowest = endif res < 0:print("0" + " " + str(lst[1]) + " " + str(lst[k]))
else:print(str(res) + " " + str(head) + " " + str(tail))
最后附上AK截图:
python
C++:
写在后面:
本题题眼在于“最大连续子数组和”,一般此类题目的解题思路都在于“差分与前缀和”这块,构建前缀和数组,通过“被减数一定,减数最小,差值(最大连续子数组和)最大”思路来进行解题,本题难度适中,核心部分可以当作模板记忆。
以上就是本题的全部内容,主要在于差分与前缀和思想的应用,对此还不清楚的童鞋可以去详细学习,当然如果您有需要,我可以出一期对于差分与前缀和讲解的文章或视频,您可以评论区留言,如果对于以上内容您有什么意见或建议,欢迎评论区交流!