动态规划专题之最大连续子序列之和

动态规划系列专题讲义

 

专题三:最大连续子序列之和

/*

 Name:  动态规划专题之最大连续子序列之和

 Author:  巧若拙

 Description:  最大连续子序列之和

给定K个整数的序列{ N1,N2, ..., NK },其任意连续子序列可表示为{Ni, Ni+1, ..., Nj },

其中 1 <= i<= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个,

例如给定序列{ -2,11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为20。 

输入:

测试输入包含若干测试用例,每个测试用例占2行,第1行给出正整数K( < 10000 ),第2行给出K个整数,中间用空格分隔。

当K为0时,输入结束,该用例不被处理。

输出:

对每个测试用例,在1行里输出最大和。若所有K个元素都是负数,则定义其最大和为0。

输入示例:

6

-2 11 -4 13 -5-2

10

-10 1 2 3 4 -5-23 3 7 -21

6

5 -8 3 2 5 0

1

10

3

-1 -5 -2

3

-1 0 -2

0

输出示例:

20

10

10

10

0

0

*/ 

#include<iostream> 

#include<string> 

 

using namespace std; 

 

const int INF = -100000; 

const int MAX = 10000; 

int B[MAX]; //B[i]用来存储包含A[i]的最大连续子序列之和

 

int MaxSubsequenceSum_1(const int A[], intn);//低效算法1    

int MaxSubsequenceSum_2(const int A[], intn);//低效算法2

int MaxSubsequenceSum_3(const int A[], intn);//分治算法

int MaxSubSum(const int A[], int left, intright);//分治算法子程序 

int MaxSubsequenceSum_4(const int A[], inti);//记忆化搜索(备忘录算法)

int MaxSubsequenceSum_5(const int A[], intn);//使用一维数组的动态规划算法 

int MaxSubsequenceSum_6(const int A[], intn);//使用一个变量代替一维数组 

int MaxSubsequenceSum_7(const int A[], intn);//使用一个变量代替一维数组的另一种写法

int MaxSubsequenceSum_8(const int A[], intn);//记录数组的同时,记录最优解和左右边界  

int MaxSubsequenceSum_9(const int A[], intn);//使用一个变量代替一维数组,输出子序列

 

 

int main() 

       intA[MAX] = {0}; 

       intn;

       cin>> n;

       while(n != 0)

       {

              for(int i=0; i<n; i++)

                     cin>> A[i];

              cout<< MaxSubsequenceSum_1(A, n) << endl; 

           cout << MaxSubsequenceSum_2(A, n)<< endl;  

           cout << MaxSubsequenceSum_3(A, n)<< endl;

          

              for(int i=0; i<n; i++)

                     B[i]= INF;

           MaxSubsequenceSum_4(A, n-1);

              intmaxSum = max(0, B[0]);  //注意全是负数的情形

           for (int i=1; i<n; i++)//存储各连续子序列的最大和     

           { 

               if (B[i] > maxSum) 

                  maxSum = B[i]; 

           } 

           cout << maxSum << endl;

          

           cout << MaxSubsequenceSum_5(A, n)<< endl; 

           cout << MaxSubsequenceSum_6(A, n)<< endl; 

           cout << MaxSubsequenceSum_7(A, n)<< endl; 

           cout << MaxSubsequenceSum_8(A, n)<< endl; 

           cout << MaxSubsequenceSum_9(A, n)<< endl; 

          

           cin >> n;

       }

     

   return 0; 

 

算法1:低效算法1:三重循环    

int MaxSubsequenceSum_1(const int A[], intn)//低效算法1  

   int sum, maxSum = 0;   

     

   for (int i=0; i<n; i++) 

   { 

       for (int j=i; j<n; j++) 

       { 

           sum = 0; 

           for (int k=i; k<=j; k++)  //语句1 

           { 

                sum += A[k]; 

           } 

           if (sum > maxSum) 

                maxSum = sum; 

       } 

   }  

     

   return maxSum; 

}

 

问题1:语句1所在循环体的作用计算连续子序列A[i..j]之和,但三重循环循环看上去还是有些低效,注意变量sum的作用,想想能否去掉语句1所在循环体,提高效率?

 

答案:

问题1:详见算法2。

 

算法2:低效算法2:二重循环

int MaxSubsequenceSum_2(const int A[], intn)//低效算法2 

   int sum, maxSum = 0;

     

   for (int i=0; i<n; i++) 

   { 

       sum =  //语句1

       for (int j=i; j<n; j++) 

       { 

           sum +=   //语句2

           if (sum > maxSum) 

                maxSum =  //语句3

       } 

   }  

     

   return maxSum; 

 

问题1:将语句1,语句2和语句3补充完整。

 

答案:

问题1:语句1:sum = 0; 语句2:sum += A[j]; 语句3:maxSum = sum;

 

算法3::分治算法

int MaxSubsequenceSum_3(const int A[], intn)//分治算法 

   return MaxSubSum(A, 0, n-1); 

 

int MaxSubSum(const int A[], int left, intright)//分治算法子程序 

   int maxLeftSum, maxRightSum; 

   int maxLeftBorderSum, maxRightBorderSum; 

   int leftBorderSum, rightBorderSum;

     

   if (left == right) 

       return (A[left] > 0) ? A[left] : 0; 

         

   int mid = (left + right) / 2; 

   maxLeftSum = MaxSubSum(A, left, mid);  

   maxRightSum = MaxSubSum(A, mid+1, right);  

     

   maxLeftBorderSum = leftBorderSum = 0; 

   for (int i=mid; i>=left; i--) //语句1 

   { 

       leftBorderSum += A[i]; 

       if (leftBorderSum > maxLeftBorderSum) 

           maxLeftBorderSum = leftBorderSum; 

   } 

     

   maxRightBorderSum = rightBorderSum = 0; 

   for (int i=mid+1; i<=right; i++) 

   { 

       rightBorderSum += A[i]; 

       if (rightBorderSum > maxRightBorderSum) 

           maxRightBorderSum = rightBorderSum; 

   } 

     

   return max(max(maxLeftSum, maxRightSum), maxLeftBorderSum+maxRightBorderSum); 

}  

 

问题1:能否把语句1改为for (int i=left; i<=mid; i++)?为什么?

 

答案:

问题1:不能。因为语句1的作用是从中间开始向左计算包含A[mid]子序列的最大和。

 

算法4:记忆化搜索(备忘录算法),需要用到全局变量B[MAX] 

int MaxSubsequenceSum_4(const int A[], inti)  

{   //B[i]用来存储包含A[i]的最大连续子序列之和

   if (B[i] != INF)

           return B[i];

   if (i == 0)  //边界条件

    {

              B[i]= A[0];

       }

       else

       {

              if(MaxSubsequenceSum_4(A, i-1) > 0) 

                     B[i]=   //语句1

              else

                     B[i]= A[i];

       }

      

   return B[i];   

 

问题1:将语句1补充完整。

答案:

问题1:语句1:B[i] = MaxSubsequenceSum_4(A, i-1) + A[i];

 

算法5:使用备忘录数组的动态规划算法 

int MaxSubsequenceSum_4(const int A[], intn)//使用备忘录数组的动态规划算法   

{   

   int S[MAX] = {A[0]};//S[i]用来存储包含A[i]的最大连续子序列之和    

  

   for (int i=1; i<n; i++)     

   {   

       if (S[i-1] > 0)      

           S[i] =   //语句1

       else       

            S[i] = A[i];     

   }    

     

   int maxSum = max(0, S[0]);  //语句2

   for (int i=1; i<n; i++)     

   { 

       if (S[i] > maxSum) 

          maxSum = S[i]; 

   } 

       

   return maxSum;   

 

问题1:将语句1补充完整。

问题2:能否把语句1改为int maxSum = S[0];?为什么?

问题3:注意到算法4中S[i]的值只与S[i-1]有关,故无需把所有的S[i]都记录下来,可以进行降维优化,用变量sum代替S[i]实现相关功能。试着实现相关代码。

 

答案:

问题1:语句1:S[i] = S[i-1] + A[i];

问题2:不能。要考虑到数组A的元素全是负数的情形。

问题:3:详见算法6。

 

算法6:使用一个变量代替备忘录数组

int MaxSubsequenceSum_5(const int A[], intn)//使用一个变量代替备忘录数组

   int sum = A[0]; 

   int maxSum = max(0, sum);

   

   for (int i=1; i<n; i++)    

   {   

       if (sum > 0)  

       {  

           sum += A[i];   

           if (sum > maxSum)

                maxSum = sum;

       } 

        else  

       {  

           sum = A[i];

       } 

   }  

     

   return maxSum; 

}  

 

算法7:使用一个变量代替备忘录数组的另一种算法

int MaxSubsequenceSum_7(const int A[], intn)//使用一个变量代替一维数组的另一种写法

   int sum = 0; 

   int maxSum = max(0, sum); //注意全是负数的情形

   

   for (int i=0; i<n; i++)//存储各连续子序列的最大和     

   {   

              sum+= A[i];

       if (sum > maxSum)   //更新最大值

       {  

           maxSum =      //语句1

       } 

       else if (sum < 0)   //重新开始    

       {  

           sum =    //语句2

       } 

   }  

     

   return maxSum; 

}

 

问题1:将语句1和语句2补充完整。

问题2:算法6和算法7的思路有哪些不同之处?

 

答案:

问题1:语句1:m = sum;

          语句2:sum = 0;

问题2:算法6先判断sum 是否大于0,再根据情况更新sum的值;算法7先默认将A[i]的值加到sum上,再判断其是否大于0,然后根据情况更新maxSum 或sum的值。即算法6是先判断再赋值,算法7是先给定一个默认值,再判断是否改变该默认值,两种思路都很常见,但算法6的逻辑更严谨,思路更清晰。

 

拓展练习:原题只要求计算最大连续子序列之和,而没有把对应的连续子序列输出来,现在要求:

1.      在算法5 MaxSubsequenceSum _5()的基础上,编写函数int MaxSubsequenceSum_8(const int A[], int n)//计算最大连续子序列之和,并输出对应的连续子序列。

2.  在算法6 MaxSubsequenceSum_6()的基础上,编写函数int MaxSubsequenceSum_9(constint A[], int n)//计算最大连续子序列之和,并输出对应的连续子序列。

 

参考答案:

int MaxSubsequenceSum_8(const int A[], intn)//记录数组的同时,记录最优解和左右边界  

{   

   int S[MAX] = {A[0]};//S[i]用来存储包含A[i]的最大连续子序列之和  

   int maxSum = max(0, S[0]); 

   int left = 0, mLeft = 0, right = 0; //mLeft和right分别存储最大连续子序列的左右边界  

     

   for (int i=1; i<n; i++)//存储各连续子序列的最大和     

   {   

       if (S[i-1] > 0) //若之前的连续子序列之和大于0,则把A[i]累加上去   

       {  

           S[i] = S[i-1] + A[i];   

           if (S[i] > maxSum)  

           { 

               maxSum = S[i]; 

                mLeft = left; 

                right = i; 

           } 

       } 

       else   //否则重新开始    

       {  

           S[i] = A[i];   

           left = i; 

       } 

   }    

   

       if(maxSum > 0)

       {

              cout<< "A[" << mLeft << ":" << right<< "] : "; 

           for (int i=mLeft; i<=right; i++)//存储各连续子序列的最大和     

           { 

               cout << A[i] << ""; 

           } 

           cout << "= "; 

       }

     

   return maxSum;   

}   

 

int MaxSubsequenceSum_9(const int A[], intn)//使用一个变量代替一维数组,输出子序列  

{   

   int sum = A[0]; 

   int maxSum = max(0, sum); 

   int left = 0, mLeft = 0, right = 0; //mLeft和right分别存储最大连续子序列的左右边界  

     

   for (int i=1; i<n; i++)//存储各连续子序列的最大和     

   {   

       if (sum > 0) //若之前的连续子序列之和大于0,则把A[i]累加上去   

       {  

           sum += A[i];   

           if (sum > maxSum)  

           { 

                maxSum = sum; 

                mLeft = left; 

                right = i; 

           } 

       } 

       else   //否则重新开始    

       {  

           sum = A[i];   

           left = i; 

       } 

   }    

   

       if(maxSum > 0) 

       {

              cout<< "A[" << mLeft << ":" << right<< "] : "; 

           for (int i=mLeft; i<=right; i++)      

           { 

               cout << A[i] << ""; 

           } 

           cout << "= "; 

       }

     

   return maxSum;   

}

 

课后练习:

练习1:1768_最大子矩阵

题目描述:已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵。

比如,如下4 * 4的矩阵

0 -2 -7 0

9 2 -6 2

-4 1 -4 1

-1 8 0 -2

的最大子矩阵是

9 2

-4 1

-1 8

这个子矩阵的大小是15。

输入

输入是一个N * N的矩阵。输入的第一行给出N (0 < N <= 100)。再后面的若干行中,依次(首先从左到右给出第一行的N个整数,再从左到右给出第二行的N个整数……)给出矩阵中的N2个整数,整数之间由空白字符分隔(空格或者空行)。已知矩阵中整数的范围都在[-127,127]。

输出

输出最大子矩阵的大小。

样例输入

4

0 -2 -7 0

9 2 -6 2

-4 1 -4 1

-1 8 0 -2

样例输出

15

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值