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

专题三:最大连续子序列之和
/* 
  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 MAX = 10000;  
  
int MaxSubsequenceSum_1(const int A[], int n);//低效算法1     
int MaxSubsequenceSum_2(const int A[], int n);//低效算法2
int MaxSubsequenceSum_3(const int A[], int n);//分治算法 
int MaxSubSum(const int A[], int left, int right);//分治算法子程序  
int MaxSubsequenceSum_4(const int A[], int n);//使用备忘录数组的动态规划算法  
int MaxSubsequenceSum_5(const int A[], int n);//使用一个变量代替备忘录数组    
  
int main()  
{  
	int A[MAX] = {0};  
	int n; 
	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;  
	    cout << MaxSubsequenceSum_4(A, n) << endl;  
	    cout << MaxSubsequenceSum_5(A, n) << endl;  
	    cin >> n;
	}
      
    return 0;  
}  

算法1:低效算法1:三重循环     
int MaxSubsequenceSum_1(const int A[], int n)//低效算法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[], int n)//低效算法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[], int n)//分治算法  
{  
    return MaxSubSum(A, 0, n-1);  
}  
  
int MaxSubSum(const int A[], int left, int right)//分治算法子程序  
{  
    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:使用备忘录数组的动态规划算法  
int MaxSubsequenceSum_4(const int A[], int n)//使用备忘录数组的动态规划算法    
{    
    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:详见算法5。 

算法5:使用一个变量代替备忘录数组
int MaxSubsequenceSum_5(const int A[], int n)//使用一个变量代替备忘录数组
{  
    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;  
}   

拓展练习:原题只要求计算最大连续子序列之和,而没有把对应的连续子序列输出来,现在要求在算法5 MaxSubsequenceSum_5()的基础上,编写函数int MaxSubsequenceSum_6(const int A[], int n)//计算最大连续子序列之和,并输出对应的连续子序列。

参考答案:
int MaxSubsequenceSum_6(const int A[], int n)//使用一个变量代替备忘录数组,输出子序列   
{    
    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:最大m子段和问题
题目描述:给定由 n个整数(可能为负整数)组成的序列a1,a2,a3,……,an,以及一个正整数 m,要求确定序列 a1,a2,a3,……,an的 m个不相交子段,使这m个子段的总和达到最大,求出最大和。 
输入:
每个测试用例将以两个整数m和n开始, 紧随其后的是n个整数a1,a2,a3,……,an。直到读入文件结束。
输出:
在一行上输出题目描述中所说的最大和。
输入示例:
1 3 
1 2 3
2 6
-1 4 -2 3 -2 3
输出示例:
6
8

练习2: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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值