PTA数据结构习题: Maximum Subsequence Sum (25分)最大子列和问题

题目

Given a sequence of K integers { N​1​​, N​2​​, …, N​K​​ }. A continuous subsequence is defined to be { N​i​​, N​i+1​​, …, N​j​​ } 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.

要求

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.
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.

理解

简单来讲,就是寻找一个整数数列的最大子列。
要求:
在一行里输入此数列的长度(即包含的整数个数),在下一行里输入数列元素;
在一行内输出最大子列和,以及最大子列的头尾元素。如果最大子列不唯一,则输出下标最小的子列(就是最靠前的子列);如果数组元素全是负数,则最大子列和为零,最大子列为此数组的首尾元素。

在线处理寻找最大子列和算法分析

PTA上有寻找最大子列和的问题,在MOOC浙大数据结构公开课里,老师给出了时间复杂度为n的名为在线处理的算法,算法如下:
int maxsubseqsum( int A[], int N ) //函数名返回值和参数
{ int sum, maxsum;
int i;
sum = maxsum = 0; //初始化变量
for( i = 0; i < N; i++ )
{
sum += A[i]; //求和操作
if( sum > maxsum )
maxsum = sum; //更新最大子列和
else if( sum < 0 )
sum = 0; //求和小于0,舍弃子列,重新开始
}
return maxsum;
}
理解这个算法需要一点点时间,但是简化一下,我们只需要求出数列的最大子列和,并不需要知道是哪个子列,这样的情况下,我们根本不需要列出所有子列,只需要知道有哪些元素对子列的和产生副作用即可,这就是此算法的第一个重要操作:
if( sum > maxsum )
maxsum = sum;
在有元素对子列和产生副作用时,不更新最大子列和。
但这还不够,最为关键的是第二个重要操作:
else if( sum < 0 )
sum = 0;
在副作用使子列和小于零时,舍弃前面所有元素,重新累加剩余的元素

很容易理解,当我们寻找最大子列和的时候,如果中间有一个负数,但是此负数的绝对值既小于前面所有元素之和,也小于后面所有元素之和,那么此负数就应该被包括进最大子列;再想一步,如果此负数的绝对值仅仅小于前面所有元素之和,那我们就不知道它能否被包括进最大子列,但如果此负数的绝对值大于前面所有元素的和,那么它一定不属于最大子列,此时应该保存前面的最大和,重新开始计算此负数后面的元素和

说到这,在线处理算法不难理解,我们只需要思考如何找出最大子列了。

思路

在理解在线处理算法之后,找出最大子列似乎不是一个很难的问题。通过分析,我们已经知道了何时应该新建子列,何时应该保存子列尾元素,在知道了这些之后,难点就仅仅在子列不唯一时最大子列的取舍了。

这个问题很简单,加一个条件判断就好了啊。

可是,真的有这么简单吗?

让我们来实现一下吧!

int sum, maxsum;
    
    int first1, last1;//最大子列的首尾元素 
    first1=A[0];
    last1=A[N-1];//初始赋值 
    
    int first,last,msum;//最终输出的最大子列首尾元素 
    first=A[0];
    last=A[N-1];
    msum=0;//初始赋值 
    
    sum = maxsum = 0;
    for( i = 0; i < N; i++ ) 
 {
        sum += A[i]; 
        if( sum > maxsum )
        {
           maxsum = sum; 
           last1=A[i];//更新最大子列尾元素 
        }
   
        else if( sum < 0 ) 
        {
            sum = 0;
            if(i+1<N)//由于出现了i+1,判断不要数组越界 
            {
                 if(A[i+1]>=0) 
                    first1=A[i+1];//更新最大子列首元素 
            }
        }
        
       if(msum<maxsum)//判断后续子列和大于原先子列和时,保存 
       {
            first=first1;
            last=last1;
            msum=maxsum;
       } 
 }
 
 printf("%d %d %d",msum,first,last);

可以看到,这段代码已经初步实现了我们需要的功能。在sum>maxsum时,更新maxsum,并且更新last(即队尾元素);不然,判断sum是否小于0,如果小于,那么舍弃当前最大和sum(此时sum其实是负数),接着判断下一个元素是否非负,如果是,那么将下一个元素更新为队首元素。最后,在更新的最大和maxsum大于保存的最大和msum时,保存一下。

这样的处理,使得子列不唯一时,更新子列首尾元素不会覆盖前面已取到的最大子列信息。加上主函数和输入输出,这段代码对于一般的数列,能够得到我们想要的结果。

我们提交一下,看看还有哪些不足。
在这里插入图片描述
看到结果,恍然大悟,我们确实没有考虑到负数和0的情况。

不过,等等……

测试点4,全是负数,我们的答案居然是正确的,我们明明没有考虑到负数啊,这是怎么回事?

回头看看题目:

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.

原来如此。

我们的代码没有考虑0和负数,当数列全部是负数时,我们输出的子列信息即不会更新,也不会保存,因为只有在msum<maxsum时,我们才会保存,而当数列只有负数时,maxsum不会被更改,等于我们给它的初始值0。而我们给出的初始子列信息,恰好是0,队首元素,队尾元素。

于是,我们只需要考虑一下只有负数和零的数列了。

首先,只有负数和零,sum不会大于0,那么maxsum就不会更新,队尾元素也不会更新,但队首元素是会更新的,可是也没有用。

由于msum=maxsum=0,更新后的子列信息不会被保存下来。

既然如此,那我们加一个条件判断,当队列中只有负数和零时,更新子列信息为(0,0,0),这样可以吗?

当然可以,只要能够实现,提交了就会返还正确的结果。这种方法的代码就不给出了,只需要在上面的代码基础上稍作修改就好了。

接下来,我们不使用这种简单粗暴的方法,看看能否有更巧妙的方式来解决这个问题。

回头看看代码,我们思考一下,是否有必要判断整个数列呢?

首先,我们最大的问题在于,最大子列和等于零的时候,子列信息不保存,其次,sum<=0时,last(队尾元素)不更新。

只要解决这两个问题,就好。

但比这两个问题更关键的,是如何解决。

首先,第二个问题好解决,当更新first时,顺手更新一下last,就能完美解决这个问题,并且不产生任何副作用。

那问题就只剩下最大子列信息的保存了。

首先,明确一点,

if(msum<maxsum)
    {
     first=first1;
     last=last1;
     msum=maxsum;
   }

这段是不能够随意更改的。

至于为什么,就不解释了,显得繁琐。

既然不能改,那么我们就把它分离出来,在只有负数和零时,sum只有等于0和小于0两个状态,maxsum不会改变,msum也不会改变。如果当sum=0时,我们保存子列信息,就解决了这个问题。

但是,这样会不会对其他情况产生影响呢?

我们的算法里,还有一个最关键的sum归零操作啊!

那我们只要在sum归零前操作一下,保存一下,这样也无非就是多保存了几次,对其他的情况,只要数列中有正数,那么,那么……

好像没有那么多那么了。

就算有正数,只要存在负数让sum<0,且下一个元素>=0,那么first和last就会更新,这样更新后的这两个,就会在下次循环的时候,保存,从而覆盖我们之前的最大子列信息。

既然如此,那我们再加一个判断,只有在sum=0,且msum=0的时候,才保存。即限定最大子列是0的时候才保存,避免覆盖。

这样就没问题了吧!

我们写一下代码

for( i = 0; i < N; i++ ) 
{
 sum += A[i]; 
    if( sum > maxsum )
    {
        maxsum = sum; 
        last1=A[i];
 }
    
    if( sum < 0 ) 
    {
  sum = 0;
  if(i+1<N)
  {
   if(A[i+1]>=0)
   {
    first1=A[i+1];
                last1=A[i+1];
            }
  }
 }
    if(sum==0&&msum==0)
 {
  first=first1;
  last=last1;
  msum=maxsum;
 }
   
 if(msum<maxsum)
 {
  first=first1;
  last=last1;
  msum=maxsum;
 } 
}

由于长度,只截取了重要部分,提交结果如下:

在这里插入图片描述

添加输入和初始赋值语句就可以提交了,可以看到结果是能通过验证的。

总结

暂无总结。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值