给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6
暴力:暴力列举所有可能的连续子数组,算法复杂度O(N^3)
算法1:
1 int MaxSubseqSum1(int A[], int N) 2 { 3 int ThisSum, MaxSum = 0; 4 int i,j,k; 5 6 for (i = 0; i < N; i++) //i对应子列左端位置 7 { 8 for (j = i; j < N; j++) //j对应子列右端位置 9 { 10 ThisSum = 0; 11 for (k = i; k <= j; k++) //一段子列的和 12 { 13 ThisSum += A[k]; 14 } 15 if(ThisSum > MaxSum) 16 MaxSum = ThisSum; //更新 17 } 18 } 19 return MaxSum; 20 }
每次从i加到j,我们都必须要经历k循环,i+(i+1)...j,所以每次j循环后都要经历一个k循环从i加到j,想想完全没有必要,可以直接在前一个子序
列的基础上加一个元素,所以k循环是没有必要的。
因此优化算法在相同的i不同的j只需要在j-1次的循环的基础上累加一项即可,算法复杂度更新为O(N^2)
算法2:
1 int MaxSubseqSum2(int A[], int N) 2 { 3 int ThisSum, MaxSum = 0; 4 int i,j,k; 5 6 for (i = 0; i < N; i++) //i对应子列左端位置 7 { 8 ThisSum = 0; 9 for (j = i; j < N; j++) //j对应子列右端位置 10 { 11 ThisSum += A[k]; //在上一个子序列和的基础上加一个数 12 13 if(ThisSum > MaxSum) 14 MaxSum = ThisSum; 15 } 16 } 17 return MaxSum; 18 }
算法3:分治把大问题拆成小问题,然后逐个解决,最后合并起来。
把数组一分为二,分别递归(即左右两边再分成小的左右两边)的去解决左右两边问题,得到两边的最大子列和,还有一种情况跨越边界的最大子列和,然后想要的结果就是这三个数之间的最大的那个数。算法复杂度O(NlogN)
1 int Max3( int A, int B, int C ) 2 { /* 返回3个整数中的最大值 */ 3 return A > B ? A > C ? A : C : B > C ? B : C; 4 } 5 6 int DivideAndConquer( int List[], int left, int right ) 7 { /* 分治法求List[left]到List[right]的最大子列和 */ 8 int MaxLeftSum, MaxRightSum; /* 存放左右子问题的解 */ 9 int MaxLeftBorderSum, MaxRightBorderSum; /*存放跨分界线的结果*/ 10 11 int LeftBorderSum, RightBorderSum; 12 int center, i; 13 14 if( left == right ) { /* 递归的终止条件,子列只有1个数字 */ 15 if( List[left] > 0 ) return List[left]; 16 else return 0; 17 } 18 19 /* 下面是"分"的过程 */ 20 center = ( left + right ) / 2; /* 找到中分点 */ 21 /* 递归求得两边子列的最大和 */ 22 MaxLeftSum = DivideAndConquer( List, left, center ); 23 MaxRightSum = DivideAndConquer( List, center+1, right ); 24 25 /* 下面求跨分界线的最大子列和 */ 26 MaxLeftBorderSum = 0; LeftBorderSum = 0; 27 for( i=center; i>=left; i-- ) { /* 从中线向左扫描 */ 28 LeftBorderSum += List[i]; 29 if( LeftBorderSum > MaxLeftBorderSum ) 30 MaxLeftBorderSum = LeftBorderSum; 31 } /* 左边扫描结束 */ 32 33 MaxRightBorderSum = 0; RightBorderSum = 0; 34 for( i=center+1; i<=right; i++ ) { /* 从中线向右扫描 */ 35 RightBorderSum += List[i]; 36 if( RightBorderSum > MaxRightBorderSum ) 37 MaxRightBorderSum = RightBorderSum; 38 } /* 右边扫描结束 */ 39 40 /* 下面返回"治"的结果 */ 41 return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum ); 42 } 43 44 int MaxSubseqSum3( int List[], int N ) 45 { /* 保持与前2种算法相同的函数接口 */ 46 return DivideAndConquer( List, 0, N-1 ); 47 }
算法4:贪心算法(在线处理算法)
每输入一个数据,进行即时处理,在任何一个地方停止输入,算法都能得到正确的解,即总是做出在当前看来最好的选择。
只需遍历一遍数组,算法复杂度为O(N)。
1 int MaxSubseqSum4(int A[], int N) 2 { 3 int i; 4 int ThisSum, MaxSum; 5 ThisSum = MaxSum = 0; 6 7 for (i = 0; i < N; i++) //向右累加 8 { 9 ThisSum += A[i]; 10 if (ThisSum > MaxSum) 11 MaxSum = ThisSum; //发现更大则更新 12 else if (ThisSum > MaxSum) //如果当前子序列为负,因为它不能使后边子列和增大 13 ThisSum = 0; //直接放弃累加 14 } 15 return MaxSum; 16 }
算法5:动态规划(DP)
不断更新dp[i]中的值,表示A数组中以A[i]为结尾的最大子序列和,例如A = [2,3,-6,2,4],则dp = [2,5,-1,2,6],则dp数组中的最大值就是最大子序列和就是6.
只需要遍历一遍数组,算法复杂度O(N)
1 int MaxSubseqSum1(int A[], int N) 2 { 3 int i; 4 int dp[N]; 5 int ThisSum = 0; 6 dp[0] = A[0]; 7 for (i = 1; i < N; i++) 8 { 9 if (dp[i]<0 || (i==1 && dp[0]<0)) //如果A[0]就小于0则它并不能使后边序列增大所以不累加,或者后边的子序列和中出现负值 10 { 11 dp[i] = A[i]; 12 } 13 else 14 { 15 dp[i] = dp[i - 1] + A[i]; 16 } 17 } 18 19 return Max.dp[i]; 20 }
DP是根据自己的理解写的,如有不对,请指正谢谢。