最大连续和

来源:互联网 发布:nat网络是什么意思 编辑:程序博客网 时间:2024/05/17 23:52

这个问题对我来说还挺难的,当初做DP时水过去了,但没彻底理解,这次打算好好分析一下,争取彻底搞懂。

首先,像:1 -1 2 2 3 -3 4 -4 5 -5这样的数列,想要找连续最大和,可以有很多种方法,从最慢的枚举O(n^3)到最快的动态规划O(n),毫无疑问,我们要选择复杂度低的算法。所以我这里就只分析两种:分治法O(nlogn)和动态规划O(n)。


分治法O(nlogn):

分治法一般分为如下三个步骤:
划分问题:把问题的实例划分成子问题。
递归求解:递归解决子问题。
合并问题:合并子问题的解得到原问题的解
要求连续最大和,最大的难点在于怎么处理负数,我们将数列拆分成两段后,递归得到两段中对应的最大值,那么区间中对应的单独的最大和已经求出来了,接下来要找分界点在区间中间的最大和,那么就分别从分界点从左和右找最大和,加起来与单独的最大和比较,这样就可以求出总的最大和。
int maxsum(int x, int y)//区间在[x,y){int v, left, right, maxs, m;if (y - x == 1) return a[x];//只有一个元素,直接返回m = x + (y - x) / 2;maxs = max(maxsum(x, m), maxsum(m, y)); //递归求解,分界点两边求出最大值v = 0; left = a[m - 1];for (int i = m - 1; i >= x; i--)//从分界点左边找连续的最大和{v += a[i];left = max(left, v);}v = 0; right = a[m];for (int i = m; i < y; i++)//从分界点右边找连续的最大和{v += a[i];right = max(right, v);}return max(maxs, left + right);//两边单独的最大值与分界点两边合并后最大值比较,主要考虑负数影响}

动态规划O(n):

动态规划的要点是用一个数组存储过程中的状态,有三种方法:
第一种比较简单,只要简单的按照正负思考即可,但是最后需要遍历一遍。用一个B[n]数组存储状态,如果B[k-1]<0,那么加一个负数肯定会减少,所以直接从A[k]开始,B[k]=A[k];如果b[K-1]>0,那么加一个正数肯定变大,所以B[k]=B[k-1]+A[k]。
状态转移方程为B[k]=max(B[K-1]+A[k],A[k])
void dp(){int b[1050];int max = -1;b[0] = a[0];for (int i = 1; i < n; i++){b[i] = max(b[i - 1] + a[i], a[i]);if (max < b[i])max = b[i];}printf("%d\n", max);}
第二种方法就比较难想到了,但好处是最后不用遍历一遍,可以考虑数组的第一个元素,以及最大的一段数组(A[i], ..., A[j]),和A[0]的关系,有以下三种情况:
1. 当0 = i = j 时,元素A[0]本身构成和最大的一段,ALL[0]=A[0]
2. 当0 = i < j 时,和最大的一段以A[0]开始,ALL[0]+start[1]
3. 当0 < i 时, 元素A[0]和最大的一段没有关系,ALL[0]=ALL[1]
从上面3中情况可以看出。可以将一个大问题(N个元素数组)转化为一个较小的问题(N-1个元素的数组)。
假设已经知道(A[1], ...,A[n-1])中和最大的一段数组之和为All[1],并且已经知道(A[1],...,A[n-1])中包含A[1]的和最大的一段数组为Start[1]。那么不难看出 (A[0], ..., A[n])中问题的解All[0] = max{ A[0], A[0] + start[1], All[1] }。这样ALL[0]表示的就是总的0~N的最大和。倒序DP即可。
start数组记录的就是包括当前值的连续最大和,比如1,-2,3,-2,start[3]就截断从3开始。而all数组则考虑要不要加a[k],说白了,就是三种情况:当前值不加,只有一个当前值,当前值加上前面的包含前一个值的最大和(要连续)。
状态转移方程为:all[k]=max(all[k+1],a[k],a[k]+start[k+1])
void dp(){int start, all[1050];start = a[n - 1]; all[n - 1] = a[n - 1];for (int i = n - 2; i >= 0; i--){start = max(a[i], a[i] + start); //start记录包括当前值的连续最大和,可能会从中间截断all[i] = max(all[i + 1], start); //就是三个比较,分成两个两两比较}printf("%d\n", all[0]);}
第三种结合了前两种的优点,不用遍历而且便于理解:
首先需要一个dp[k]存储第k个数之前的最大连续和,然后还要一个sum和temp,sum用来不断累加a[k],如果sum>temp,就把temp更新为sum,并且如果sum<0,将sum重新赋值为0。
void dp(){temp = minx; sum = 0;//这是一种新dp做法,用temp存储当前最大值,temp一开始赋一个极小值for (int i = 1; i <= n; i++)//注意这里如果要倒序的话必须从1开始{scanf("%d", &a[i]);sum += a[i];if (sum > temp)temp = sum; //如果加上当前值更大,更新tempdp[i] = temp;//dp存储第i个数前的最大连续和if (sum < 0)//如果sum加上当前值小于0就舍去sum = 0;}}






0 0
原创粉丝点击