mooc大学浙大数据结构——最大子列和问题

数据结构 专栏收录该内容
1 篇文章 0 订阅

题目一:最大子列和问题

给定K个整数组成的序列{ N1, N2, …, NK},“连续子列”被定义为{ Ni, Ni+1, …, Nj},其中 1≤i≤j≤K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。

本题旨在测试各种不同的算法在各种数据情况下的表现。各组测试数据特点如下:

数据1:与样例等价,测试基本正确性;
数据2:10^2个随机整数;
数据3:10^3个随机整数;
数据4:10^4个随机整数;
数据5:10^5个随机整数;

学习要点:
1、如何寻找代码中的可改进点
2、通过改进代码,得到更有效的算法
3、了解在线处理和分而治之思想
4、在线处理类似于动态规划思想

解题方法:

  1. 在线处理------O(n)
    “在线”的意思是指每输入一个数据就进行即时处理,在任何一个地方中止输入,算法都能正确给出当前的解。
    浙江大学陈越老师课件中的方法,用到了动态规划,很像数学中的递推。 我们用 d p i \mathrm{dp}_i dpi表示以第i个数结尾的最大连续子序列的和,于是存在以下递推公式:
    d p i = m a x ( 0 , d p i − 1 ) + n u m i \mathrm{dp}_i = \mathrm{max}(0, \mathrm{dp}_{i-1}) + \mathrm{num}_{i} dpi=max(0,dpi1)+numi
    【代码1】
int MaxSubseqSum1( int A[], int N) {
	int ThisSum, MaxSum;
	int i;
	ThisSum = MaxSum = 0;
	for(int i = 0; i < N; i++) {
		ThisSum += A[i]; /* 向右累加 */
		if( ThisSum > MaxSum ){
			MaxSum = ThisSum; /* 发现更大和则更新当前结果 */
		} else if ( ThisSum < 0) { // 如果当前子列和为负
			ThisSum = 0; // 则不可能使后面的部分和增大,抛弃之
		}
	}
	return MaxSum;
}

流程图表示:

Created with Raphaël 2.2.0 Start i<N 向右累加 发现更大子列和 更新当前结果 ++i End 当前子列和为负数 局部合清零 yes no yes no yes no

【代码2】递推式的实现

int MaxSubseqSum3(int A[], int N) {
	int ThisSum, MaxSum, i;
	ThisSum = A[0];
	MaxSum = (ThisSum > 0) ? ThisSum : 0;
	for (i = 1; i < N; i++) {
		if (ThisSum > 0)
			ThisSum += A[i];
		else
			ThisSum = A[i];   //实现递推式dp[n] = max(0, dp[n-1]) + num[n]
		if (ThisSum > MaxSum)
			MaxSum = ThisSum; /* 发现更大和则更新当前结果 */
	}
	return MaxSum;
}

另一个在线处理算法------O(n)
思路:以第n个数为结尾的最大子序列和有什么特点?假设这个子序列的起点是m,于是结果为sum[n] - sum[m-1]。并且,sum[m-1]必然是sum[1],sum[2]…sum[n]中的最小值!这样,我们如果在计算sum数组的时候,同时保存之前的最小值lmin, 那么只要计算sum[n] -lmin即可得到最大子序列和!
【代码3】

int MaxSubseqSum5(int A[], int N) {
	int ThisSum, MaxSum, i,lmin;
	ThisSum = MaxSum = lmin = 0;
	for (i = 0; i < N; i++) {
		ThisSum += A[i];
		if (ThisSum < lmin)
			lmin = ThisSum;
		if ((ThisSum - lmin) > MaxSum)
			MaxSum = ThisSum - lmin;
	}
	return MaxSum;
}

————————————————
版权声明:本文为CSDN博主「C小C」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/C_chuxin/article/details/84887398

题目二:复杂度2 Maximum Subsequence Sum

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 } \{ N_i, N_{i+1},..., N_j \} {Ni,Ni+1,...,Nj} where 1 ≤ i ≤ j ≤ K 1≤i≤j≤K 1ijK. 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 ) K (≤10000) K(10000). The second line contains K K 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 i i and j j j (as shown by the sample case). If all the K K 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

解题思路:
该题目为最大子列和的升级版,还要求求出子列的第一个和最后一个元素。结题重点是考虑可能的测试集:
1、有正负,负数开头结尾,有并列最大和
2、最大和序列中有负数
3、并列和对应相同i但是不同j,即尾是0,或者开头有0
4、1个正数
5、负数和0
6、全是负数
7、最大和前面有一段是0
8、最大N

解题方法:
依然采用在线处理方式,考虑修改题目一中的第一种在线处理方式,但总是不能满足测试集中的第3点,原因是因为最大和子列开头有0的情况(因要求 i i i j j j为子列中最小的下标)。想要解决该问题需要用一个临时变量单独记录该值,因为0会使序列和ThisSum不再减少,而最后发现ThisSum > MaxSum时,才用该临时变量更新当前子序列的第一个元素。于是稍微修改了一下代码1得出:
【改进代码1】

void MaxSubseqSum(int arr[], int K) {
    int this_sum, max_sum, first_num, last_num, first_num_temp;
    max_sum = -1;                        // 考虑只增不减,因此赋初值为-1
    this_sum = 0;
    first_num = first_num_temp = arr[0]; // 全为负数时要求为第一个元素
    last_num = arr[K-1];                 // 全为负数时要求为最后一个元素
    for (int i = 0; i < K; i++) {
        this_sum += arr[i];
        if(this_sum > max_sum) {
            max_sum = this_sum;
            last_num = arr[i];
            first_num = first_num_temp;   // 当前和增加到大于max_sum时更新,走到该分支时的first_num_temp是第一个让this_sum>=0的数
        } else if ( this_sum < 0) {
            this_sum = 0;
            first_num_temp = arr[i+1];    // 当前和为负值时,指向下一个数
        }
    }
    if(max_sum == -1) max_sum = 0;        // 全为负数时赋值为0
    return;
}

想到这里,与其增加一个临时变量来解决该问题,不如从后往前计算比较方便,因为根据题意,最大和子序列的尾部不可以是0,头部可以是0。因此,从后往前计算会不会更容易实现,而不增加多余的变量呢?其实并不是,只不过当this_sum >= max_sum的时候,也需要更新第一个元素罢了。下面的代码用到了递推思路,即题目一中的【代码2】的改进:
【改进代码2】

void MaxSubseqSum(int arr[], int K) {
    int this_sum, max_sum, first_num, last_num, last_num_temp;
    max_sum = std::max(arr[K-1], 0);
    this_sum = arr[K-1];
    last_num = last_num_temp = arr[K-1];
    first_num = arr[0];
    for (int i = K-2; i >= 0; i--){
        if ( this_sum > 0 ) {
            this_sum +=arr[i];
        } else {
            this_sum = arr[i];// dp[i] = max(dp[i-1], 0) + arr[i]
            if(arr[i] >= 0)
                last_num_temp = arr[i];
        }
        if ( max_sum <= this_sum ) {
            max_sum = this_sum;
            first_num = arr[i];
            last_num = last_num_temp;
        }
    }
    return;
}

那么题目一中的【代码3】可不可以改进一下加以利用呢?当然是可以的:
【改进代码3】

void MaxSubseqSum(int arr[], int K) {
    int first_num, first_num_temp, last_num;
    int this_sum, max_sum, min_sum;
    // max_sum = this_sum - min_sum;
    this_sum = min_sum = 0;
    max_sum = -1;
    first_num = first_num_temp = arr[0];
    last_num = arr[K-1];
    for (int i = 0; i < K; i++) {
        this_sum += arr[i];
        if(this_sum - min_sum > max_sum) {
            max_sum = this_sum - min_sum;
            first_num = first_num_temp;
            last_num = arr[i];
        }
        if(this_sum < min_sum) {
            min_sum = this_sum;
            first_num_temp = arr[i+1];
        }
    }
    if(max_sum == -1) max_sum = 0;
    return;
}

第一次发博客特此纪念一下,流程图绘制效果不是很满意,但没想到CSDN竟然支持本地md文件的上传,用马克飞象写的可以直接粘贴过来,体验不错。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值