题目:
输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,因此输出为该子数组的和18。
要求最大的子数组的和.我们可以想象,当遍历到其中一个数A[i],如果A[i]之前的数组(当前之和)CurrentSum => 0,那么有CurrentSum+A[i]=>A[i],则CurrentSum应该继续累加CurrentSum+=A[i].如果CurrentSum < 0,那么有CurrentSum+A[i]<A[i],那么用CurrentSum+A[i]去更新CurrentSum的结果会比用A[i]去更新CurrentSum的结果更小,于是可以用A[i]去更新CurrentSum.用Sum来记录最终的最大值.如果CurrentSum > Sum,则用CurrentSum更新Sum.
代码:
#include<iostream> #include<Limits.h> using namespace std; int MaxArray(int* A,int n) { int Sum=INT_MIN; int CurrentSum=0; for(int i=0;i<n;++i) { if(CurrentSum < 0)//如果i之前的和为负,则抛弃前边的,从A[i]开始统计新的和 CurrentSum = A[i]; else //如果i之前的为正,则继续累加 CurrentSum += A[i]; if(Sum < CurrentSum) Sum=CurrentSum; } return Sum; } void main() { int A[]={ -2,11, 11, 9, -7, -2, -5,-1}; int len=sizeof(A)/sizeof(A[0]); cout<<MaxArray(A,len); }
实训演练:http://acm.hdu.edu.cn/showproblem.php?pid=1003
这道题除了要求求出最小和之外,还需要求出最小数组的起始位置,一样可以类比,curSum 和Sum,可以想到用curBeg,curEnd, 和beg,end来实现。
//http://acm.hdu.edu.cn/showproblem.php?pid=1003 #include<stdio.h> #include<limits.h> void MaxSum(int* A,int n,int& max,int& beg,int& end) { int curSum=0; int curBeg,curEnd; max= INT_MIN; curEnd=curBeg=beg=1; for(int i=0;i<n;++i) { if( curSum > 0) //if(curSum > 0) curSum + A[i] > A[i] { curSum += A[i]; } else // curSum + A[i] < A[i],用A[i]更新curSum { if( curSum < 0) //if (curSum == 0)不需要更新curBeg curBeg = i+1; curSum = A[i]; } if( curSum > max ) { max = curSum ; beg = curBeg ; end = i+1; } } } int main() { int A[100000]; int n ,ArrayNum,max,beg,end; scanf("%d",&n) ; for(int i=1;i<=n;++i) { scanf("%d",&ArrayNum); for(int j=0;j<ArrayNum;++j) scanf("%d",&A[j]); printf("Case %d:\n",i); MaxSum(A,ArrayNum,max,beg,end); printf("%d %d %d\n",max,beg,end); if( i != n) printf("\n"); } return 0; }
测试用例:(全非负,全负,有正有负)
input:
40 0 2 0
6 2 7 -9 5 4 3
4 0 0 -1 0
7 -1 -2 -3 -2 -5 -1 -2
6 -1 -2 -3 1 2 3
5 -3 -2 -1 -2 -3
output:
21 3
121 6
01 1
-11 1
64 6
-13 3
202 6
6 -1 2 3 4 5 6
另一道OJ题目:http://acm.hdu.edu.cn/showproblem.php?pid=1231
#include<stdio.h> #include<limits.h> int A[10005],K; int LongSubArr(int& sum,int& beg,int& end) { sum = INT_MIN; int i,cursum = 0,curbeg=0; for(i=0; i<K; ++i) { if( cursum < 0 ) { cursum = A[i]; curbeg = i; } else cursum += A[i]; if( cursum > sum ) { sum = cursum; beg = curbeg; end = i; } } return sum; } int main() { int i,sum,beg,end; while(scanf("%d",&K)!=EOF && K !=0 ) { for(i=0; i<K; ++i) scanf("%d",&A[i]); LongSubArr(sum,beg,end); if( sum < 0 ) printf("0 %d %d\n",A[0],A[K-1]); else printf("%d %d %d\n",sum,A[beg],A[end]); } }
面试的一道经典数组题目:在一个数组中,找到满足以下条件的所有这些数字,使得它比数组左边的数字都大,
同时比数组右边的数字都小.例如:A[]={1,4,3,7,8,11,65},1,7,8,11,65都是满足条件的数字,因为它们都比左边
的数字大比右边的数字小.这道题也有很简单的思路,蛮力法就可以轻松解决,遍历到的每一个数字,只需要判断
它的左边是否都比它小,它的右边都比它大,然后输出所有满足条件的数字即可,时间复杂度显然是O(n^2).但是这
种办法显然达不到面试官的要求,肯定有时间复杂度更低的算法.用DP思想,我们假定遍历到数组第k位时,第k位之前
(包括k)的最大值是j,表示为DP[k]=j,即k位之前的最大值为j.接下来的任务就是找初始状态和状态转移式(DP的两个基本点)
初态:DP[0]=A[0](显然成立)
转移式:DP[k]=max{ DP[k-1],A[k] }.
有了这两点,就可以构造DP[]数组(LeftMax)了.用同样的办法也可以构造出右边的最小值DP数组(RightMin).然后接下来
的工作就是比较LeftMax和RightMin数组对应下标的值是否相同,如果相同,说明这个值既是左边的最大值,又是右边的最小值,
就是我们要求的结果.其实,LeftMax和RightMin只需要求出其中一个即可,另一个无须保存,只需要用o(1)的一个变量存储即可.
//YY二面数组找一个值比左边的都大,比右边的都小. #include<stdio.h> #include<limits.h> int RightMin[100],A[100]={1,4,3,7,8,11,65}; void Find(int n) { int i; RightMin[n-1]=A[n-1]; for(i=n-2;i>=0;--i) { if( A[i] < RightMin[i+1]) RightMin[i]=A[i]; else RightMin[i]=RightMin[i+1]; } int LeftMax=INT_MIN; for(i=0;i<n;++i) { if( A[i] > LeftMax) LeftMax=A[i]; if( LeftMax == RightMin[i]) printf("%d ",A[i]); } } int main() { Find(7); return 0; }