最大子序列和问题逐步优化从O(n^3)---O(n)

问题:

     给定一个序列A1,A2,A3,........An(可能有负数),求A1~An的一个子序列Ai~Aj,使得Ai到Aj的和最大 。

如整数序列-2, 11, -4, 13, -5, 2, -5, -3, 12, -9的最大子序列的和为21。

   方法1:最简单的一个方法就是枚举每个子序列,然后求出每个子序列的和,再取其中的最大值;

时间复杂度为n^3;代码如下:

int sum1()
{
    int i,j,k,Maxsum=0;
    for(i=0;i<n;i++)
       for(j=i;j<n;j++)
       {
             int temp=0;
             for(k=i;k<=j;k++)
                  temp+=a[k];
             if(temp>Maxsum)
                   Maxsum=temp;
       }
     return Maxsum;
}

方法2:上面每枚举一个子序列我们都要遍历一边这个子序列来求出和,其实这是不必要的;令sum(i,j)表示从Ai--Aj的子序列的和,那么sum(i,j+1)=sum(i,j)+A(j+1);所以对于同一个起点的子序列,我们就可以使用这个递推来减少一重循环了。这样时间复杂度就成了n^2啦。 代码如下:


int sum2()
{
     int i,j,Maxsum;
     for(i=0;i<n;i++)
     {
           int temp=0;
           for(j=i;j<n;j++)
           {
                temp+=a[j];
                if(temp>Maxsum)
                    Maxsum=temp;
           }
     }
     return Maxsum;
}

方法3:如果我们把这个序列分成2半,那么最大和的子序列就会有3个情况;1.在左边序列,2.在右边序列,3.左边有一些,右边也有一些;对于第一第二种情况,我们可以通过递归的方法求解,对于第三种情况,我们可以求出左边序列从最后一个元素开始的最大连续和,右边序列从第一个元素开始的最大连续和;然后将他们加起来。总的来说就是分治的思想,时间复杂度为NlogN。代码如下:


int sum3(int left,int right)
{
      if(left==right)
      {
         if(a[left]>0)
             return a[left];
         else
             return 0;
      }
      int Maxsum=0;
      Maxsum=max(Maxsum,sum3(left,(left+right)/2));
      Maxsum=max(Maxsum,sum3((left+right)/2+1,right));
      int Maxleftsum=0,Maxrightsum=0,temp=0;
      for(i=(left+right)/2;i>=left;i--)
      {
            temp+=a[i];
            if(temp>a[i])
                Maxleftsum=temp;
      }
      temp=0;
      for(i=(left+right)/2+1;i<=right;i++)
      {
            temp+=a[i];
            if(temp>Maxrightsum)
                  Maxrightsum=temp;
      }
      if(Maxrightsum+Maxleftsum>Maxsum)
           Maxsum=Maxrightsum+Maxleftsum;
      return Maxsum;
}


方法4:算法的大概思路是,从前往后扫,记录出现的最大子序列和,但是如果当前记录的子序列和小于0,就将其置零

从当前位置的下一个位置从新开始记录。时间复杂度显然是O(n),算法的正确性证明是引用别人的。代码如下:


int sum4()
{
     int Maxsum=0,i,temp=0;
     for(i=0;i<n;i++)
    {
           temp+=a[i];
           if(temp>Maxsum)
                Maxsum=temp;
          if(temp<0)
                temp=0;
    }
    return sum;
}

算法正确性证明如下:


    很容易理解时间界O(N) 是正确的,但是要是弄明白为什么正确就比较费力了。其实这个是算法二的一个改进。分析的时候也是i代表当前序列的起点,j代表当前序列的终点。如果我们不需要知道最佳子序列的位置,那么i就可以优化掉。

    重点的一个思想是:如果a[i]是负数那么它不可能代表最有序列的起点,因为任何包含a[i]的作为起点的子序列都可以通过用a[i+1]作为起点来改进。类似的有,任何的负的子序列不可能是最优子序列的前缀。例如说,循环中我们检测到从a[i]a[j]的子序列是负数,那么我们就可以推进i关键的结论是我们不仅可以把i推进到i+1,而且我们实际可以把它一直推进到j+1

     举例来说,令 p i+1 j 之间的任何一个下标,由于前面假设了 a[i]+…+a[j] 是负数,则开始于下标 p 的任意子序列都不会大于在下标 i 并且包含从 a[i] a[p-1] 的子序列对应的子序列( j 是使得从下标 i 开始成为负数的第一个下标)。因此,把 i 推进到 j+1 是安全的,不会错过最优解。 注意的是:虽然,如果有以a[j]结尾的某序列和是负数就表明了这个序列中的任何一个数不可能是与a[j]后面的数形成的最大子序列的开头,但是并不表明a[j]前面的某个序列就不是最大序列,也就是说不能确定最大子序列在a[j]前还是a[j]后,即最大子序列位置不能求出。但是能确保maxSum的值是当前最大的子序列和。 这个算法还有一个有点就是,它只对数据进行一次扫描,一旦 a[j] 被读入处理就不需要再记忆。它是一个 联机算法




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值