问题描述:给定一个数组A[0,1…n-1],求A的连续子数组,使该数组和最大
一. 暴力法
分析:首先初始化要求的最大值maxSum为A[0],然后定义三个索引i、j、k,然后三层循环:第一层i从0遍历到n-1,第二层j从i遍历到n-1,第三层k从i遍历到j,求出A[i]到A[j]之间的元素的和,然后和maxSum作比较并更新maxSum。
复杂度:时间复杂度为O(n^3),空间复杂度O(1)
代码:
- int MaxSubArray(int A[], int n)
- {
- int maxSum = A[0];
- int currSum;
- for(int i = 0; i < n; i++)
- {
- for(int j = i; j < n; j++)
- {
- currSum = 0;
- for(int k = i; k <=j; k++)
- {
- currSum += A[k];
- }
- if(currSum > maxSum)
- maxSum = currSum;
- }
- }
- return maxSum;
- }
二. 暴力法优化版本
分析:在第一个版本中,由于在第二层循环遍历过程中,如果j增加1变为j+1,k从i到j又要重复算一次,其实如果优化一下,将前面的从A[i]到[j]计算的和保留下来,而此时遍历到j+1时,只需要用前面的保留值再加上A[j+1]即得到了从i到j+1的部分子数组和。
复杂度:时间复杂度O(n^2),空间复杂度O(1)
代码:
- int MaxSubArray(int A[], int n)
- {
- int maxSum = A[0];
- int currSum;
- for(int i = 0; i < n; i++)
- {
- currSum = 0;
- for(int j = i; j < n; j++)
- {
- currSum += A[j];
- if(currSum > maxSum)
- {
- maxSum = currSum;
- }
- }
- }
- return maxSum;
- }
三. 分治法
分析:讲数组从中间分开,则最大子数组要么完全在组半边数组,要么在有半边数组,要么跨立在中间的分界点上,如果完全在左右半边数组,用递归解决,如果跨立在分界点上,则一定包含左半边数组的最大后缀和右半边数组的最大前缀,因此可以从分界处向前后扫。
复杂度:时间复杂度O(nlogn),空间复杂度O(1)
代码:
- int MaxSubArray(int A[], int from, int n)
- {
- if(from == to) return A[from];
- int mid = (from + to) >> 1;
- int m1 = MaxSubArray(A, from, mid);
- int m2 = MaxSubArray(A, mid + 1, to);
- int left = A[mid];
- int now = A[mid];
- for(int i = mid - 1; i >=from; i--)
- {
- now += A[i];
- left = max(left, now);
- }
- int right = A[mid + 1];
- now = A[mid + 1];
- for(int j = mid + 2; j <= to; j++)
- {
- now += A[j];
- right = max(right, now);
- }
- int m3 = left + right;
- int maxSum = max(m1, m2, m3);
- return maxSum;
- }
四. sum数组法
分析:
设sum[i] = A[0] +A[1] + …… +A[i]
记S[i, j]为从子数组A[i],…..A[j]的和,则S[i, j] = sum[j] - sum[i-1],
如何求出最大的S[i, j],一个很直观的想法就是再遍历j的时候,我们使得sum[i-1]保持最小,即可得到在j在当前的最小子数组和,另外每遍历一次j,我们就像当前的到的S[i, j]和保留值作比较,并更新maxSum。
复杂度:时间复杂度O(n),空间复杂度O(n)
代码:
- int MaxSubArray(int A[], int n)
- {
- int sum[n];
- sum[0] = A[0];
- for(int i = 1; i < n; i++)
- sum[i] = sum[i-1] + A[i];
- int maxSum = sum[0];
- int min = 0;
- for(int j = 1; j < n; j++)
- {
- if(sum[j-1] < min) min = sum[j-1];
- if(sum[j] - min > maxSum) maxSum = sum[j] - min;
- }
- return maxSum;
- }
五. 动态规划
分析:
上面的算法将时间复杂度降到了O(n),却将空间复杂度升到了O(1),那么能不能讲空间复杂度降到O(1)呢 答案是肯定的。
不妨设all[i]为子数组A[0]…..A[i]的最大和,start[i]为子数组A[0]…..A[i]且包含A[i]的的最大和,那么如何求出all[i]呢,观察可得,all[i] = max{all[i-1], start[i-1] + A[i], A[i]}
复杂度:时间复杂度O(n),空间复杂度O(1)
代码:
- int MaxSubArray(int A[], int n)
- {
- int all[n];
- int start[n];
- all[0] = start[0] = A[0];
- for(int i = 1; i < n; i++)
- {
- start[i] = max(start[i-1]+ A[i], A[i]);
- all[i] = max(start[i], all[i-1]);
- }
- return all[n-1];
- }
似乎空间复杂度没降下来,但其实以上程序中的数组是没必要的。
- int MaxSubArray(int A[], int n)
- {
- int nall;
- int nstart;
- nall = nstart = A[0];
- for(int i = 1; i < n; i++)
- {
- nstart = max(nstart + A[i], A[i]);
- nall = max(nstart, nall);
- }
- return nall;
- }