【问题描述】
在长度为N的整形数组中,求连续子串的和的最大值,要求复杂度为O(N)。
例如:1 2 3 -1 -20 100 34,结果为134。
【分析】
[思路一]
自己最初的想法,利用回溯的思想,从下标为0的元素开始遍历,用former保存当前最大的连续子集的和,当sum小于0的时候开始回溯,即删除这个元素,删除到已遍历数组最右边的那个正整数为止(用下标j标识)。事后觉得这个算法比较复杂,因为要维护下标j的指向,以及还要单独求出数组中的最大值,并最后与former进行比较。因此这个算法只是实现了功能,此算法的效率和代码量都不好。
- #include <cstdio>
- #include <cassert>
- int MaxAdjVal(int a[], int len)
- {
- int sum=0, maxval=a[0], former=0;
- assert(len);// check
- for (int i=0,j=0,i_tmp,flag=true; i<len; ++i)
- {
- maxval<a[i] ? maxval=a[i] : NULL;// save biggest value
- if (a[i]<0 && flag)
- {
- j=i-1;// index of last positive number
- flag=false;
- }
- a[i]>0 ? flag=true : NULL;
- sum+=a[i];
- if (sum<=0)
- {
- sum-=a[i];// get rid of this element
- i_tmp=i;
- while (--i_tmp>j && sum>0)
- {
- sum-=a[i_tmp];
- }
- if (former<sum)
- {
- former=sum;
- }
- sum=0;// reset
- }
- }
- return maxval>0 ? (sum>former? sum:former) : maxval;
- }
- int main()
- {
- int a[100]={0}, i=0, res=0;
- printf("Enter some numbers (Ctrl+z to end):/n");
- while((1==scanf("%d",&a[i])) && ++i<100);
- rewind(stdin);
- res=MaxAdjVal(a,i);
- printf("%d/n",res);
- return 0;
- }
[思路二]
思路二对算法进行了简化,不采用回溯,只用maxsum保存当前最大的连续子集的和,如果数组全为负数,找一个最大值就可以了。注意当cursum小于0的时候,要将cursum置为0。
一个无序的数组,找出相邻的任意个元素,使得其和最大。要得O(n)的复杂度,顺序读取,任意步骤都是当前读取的序列判断最优解,此谓联机算法。(参考:http://blog.csdn.net/hairetz/archive/2009/10/15/4676994.aspx )
用vector可以描述如下:
- int maxsubsum(const vector<int>& vec)
- {
- int maxsum = 0, cursum = 0;
- for (int i = 0; i < vec.size(); ++i)
- {
- cursum += vec[i];
- if (cursum > maxsum)
- maxsum = cursum;
- else if (cursum < 0)
- cursum = 0;
- }
- return maxsum;
- }
用C语言描述如下:
- #include <cstdio>
- #include <cassert>
- int MaxAdjVal(int a[], int len)
- {
- assert(len);// check
- int sum=0, max=a[0];
- for(int i=0; i<len; ++i)
- {
- sum+=a[i];
- if (sum>max)
- max=sum;
- if (sum<0)
- sum=0;
- }
- return max;
- }
- int main()
- {
- int a[100]={0}, i=0, res=0;
- printf("Enter some numbers (Ctrl+z to end):/n");
- while((1==scanf("%d",&a[i])) && ++i<100);
- rewind(stdin);
- res=MaxAdjVal(a,i);
- printf("%d/n",res);
- return 0;
- }
【问题扩展】
1. 问这样的子串有多少个。
2. 如果是首尾相连的,那么最大子串和是多少,有多少个。
3. 如果是首尾相连的,取两个不相交子串,那么最大子串和是多少。
4. 若存在多个最大子串和,则输出第一个最大子串和的起始和终止下标。
【参考】
(1) 经典的DP问题。
请参考这道ACM题:http://acm.hdu.edu.cn/showproblem.php?pid=1003
- #include <iostream>
- using namespace std;
- void MaxSub(int *arr, int count, int &first, int &last, int ∑);
- int main()
- {
- int num, n, i, j;
- int *arr, first, last, sum;
- bool top = true;
- cin >> num;
- for(j=0;j<num;j++)
- {
- cin >> n;
- arr = new int[n];
- for(i=0;i<n;i++)
- cin >> arr[i];
- MaxSub(arr, n, first, last, sum);
- if(top)
- top = false;
- else
- cout << endl;
- cout << "Case " << j+1 << ":" << endl
- << sum << " " << first+1 << " "
- << last+1 << endl;
- delete [] arr;
- }
- return 0;
- }
- //求最大子序列之和,arr是原来的数组,count是数组的元素个数,
- //first,last是所求子序列的第一个元素,最后一个元素的下标,sum是所求子序列的元素之和
- void MaxSub(int *arr, int count, int &first, int &last, int ∑)
- {
- //tFirst,tSum为临时的最大子序列的第一个元素与和
- int tFirst, tSum, i;
- //初始化
- first = tFirst = last = 0;
- sum = tSum = arr[0];
- //遍历该数组
- for(i=1;i<count;i++)
- {
- //如果tSum大于,则将它与相加,否则,arr[i]为子序列的第一个元素
- if(tSum >= 0)
- tSum += arr[i];
- else
- {
- tSum = arr[i];
- tFirst = i;
- }
- //如果临时的最大值大于实际的最大值,则更新子序列的first, last, sum
- if(sum < tSum)
- {
- sum = tSum;
- first = tFirst;
- last = i;
- }
- }
- }
(2) 一网友的几种解决方法:
- #include<iostream>
- using namespace std;
- #define NUM 10
- template<class ElemType>
- //普通2层循环时间复杂度 O(n*n)
- void MaxSum(ElemType*p,int& beg,int& end,int& sum)
- {
- ElemType count=0;
- sum=count;
- for(int i=0;i<NUM-1;i++)
- {
- count=0;
- for(int j=i;j<=NUM-1;j++)
- {
- count+=p[j];
- if (count>sum)
- {
- beg=i;
- end=j;
- sum=count;
- }
- }
- }
- }
- //利用分治算法时间复杂度O(n)
- template<class ElemType>
- void MaxSum1(ElemType*p,int low,int high,int& beg,int& end,ElemType& sum)
- {
- if(low==high)
- {
- sum=(p[low]>0)?p[low]:0;
- }
- else
- {
- int i;
- int mid=(low+high)/2;
- int leftStart,leftEnd; //左边区间最大子段的起止位置
- int rightStart,rightEnd; //右边区间最大子段的起止位置
- int leftSum=0; //左边区间的最大和
- int rightSum=0; //右边区间的最大和
- MaxSum1(p,low,mid,leftStart,leftEnd,leftSum);
- MaxSum1(p,mid+1,high,rightStart,rightEnd,rightSum);
- ElemType sumL=0; //跨越左右区间的左边部分的最大子段和
- ElemType curSum=0; //当前跨越左右区间的左边区间部分的子段和
- for(i=mid;i>=low;i--)
- { //处理左边区间
- curSum+=p[i];
- if (sumL<curSum)
- { //修改最大字段和和开始位置
- sumL=curSum;
- beg=i;
- }
- }
- ElemType sumR=0; //跨越左右区间的右边部分的最大子段和
- ElemType curSum1=0; //当前跨越左右区间的右边区间部分的子段和
- for(i=mid+1;i<=high;i++)
- { //处理右边区间
- curSum1+=p[i];
- if (sumR<curSum1)
- { //修改最大字段和和开始位置
- sumR=curSum1;
- end=i;
- }
- }
- sum=sumL+sumR; //跨越左右区间的最大字段和
- if (sum<=leftSum)
- {
- sum=leftSum;
- beg=leftStart;
- end=leftEnd;
- }
- if (sum<=rightSum)
- {
- sum=rightSum;
- beg=rightStart;
- end=rightEnd;
- }
- }
- }
- //利用动态规划时间复杂度也为O(n)
- template<class ElemType>
- void MaxSum2(ElemType*p,int& beg,int& end,ElemType& sum)
- {
- sum=0;
- ElemType b=0; //上界为j的最大字段和
- int bStart,bEnd; //上界为j的最大字符段和的起止位置
- for(int j=0;j<NUM;j++)
- { //循环求最大字段和
- if (b>0)
- {
- b+=p[j];
- bEnd=j;
- }
- else
- {
- b=p[j];
- bStart=j;
- bEnd=j;
- }
- if (b>sum) //判断当前子段和,之前最大子段和的大小
- {
- sum=b;
- beg=bStart;
- end=bEnd;
- }
- }
- }
- void main()
- {
- int p[NUM]={-4,2,4,2,-2,8,9,-4,3,-2};
- int beg,end,sum;
- beg=end=sum=0;
- // MaxSum<int>(p,beg,end,sum);
- // MaxSum1<int>(p,0,NUM-1,beg,end,sum);
- MaxSum2<int>(p,beg,end,sum);
- cout<<"开始下标为:"<<beg<<endl;
- cout<<"结束下标为:"<<end<<endl;
- cout<<"最大字段和为:"<<sum<<endl;
- }
(3) 参考《编程之美》
(4) 关于此问题的讨论。
http://topic.csdn.net/u/20100929/09/b9c84138-b5d2-4cfa-bcd4-140ffedfbc11.html?seed=1620195128&r=68870831#r_68870831