引言
?
?
本文用了五种方法来求解这道题,从最初的n^3的复杂度到后面的n的复杂度,分别用到了递推公式、分而治之以及动态规划的方法去一步步降低算法的复杂度。
?
?
解题报告
?
?
首先我们很容易想到的一个解法就是三层遍历,首先子数组必定是连续的一串值,相当于从原数组array的下标范围0~n-1中选出i和j,去算arra[i]~array[j]的和,于是我们可以得到最初的第一个解法
?
?
public static int sol1(int[] array) {
int lenght = array.length;
int result = Integer.MIN_VALUE;
int tempSum = 0;
for (int i = 0; i < lenght; i++) {
for (int j = i; j < lenght; j++) {
tempSum = 0;
for (int k = i; k <= j; k++) {
tempSum += array[k];
}
if (tempSum > result) {
result = tempSum;
}
}
}
return result;
}
?
?
接下来,由于array[i]~array[j]的和可以由array[i]~array[j-1]的和再与array[j]相加得到,所以我们可以将上面的代码减少一层循环,从而将复杂度降低到n^2
?
?
public static int sol2(int[] array) {
int lenght = array.length;
int result = Integer.MIN_VALUE;
for (int i = 0; i < lenght; i++) {
int tempSum = 0;
for (int j = i; j < lenght; j++) {
tempSum += array[j];
if (tempSum > result) {
result = tempSum;
}
}
}
return result;
}
?
?
现在已经降低到n^2了,但是我们并没有用到分而治之的方法,一般这种二分法可以将复杂度再一次降低到nlogn,那么如何去使用呢。
?
?
考虑将所给数组array(array[0]~array[n-1])分为长度相等的两段数组(array[0],...,array[n/2-1])和(array[n/2],...,array[n-1])
?
?
然后分别求出这两段数组各自最大子段和,得到 MaxLeftBorderSum和MaxRightBorderSum
?
?
则原数组(array[0],...,array[n-1])的最大子段和分为以下三种情况:
?
?
a.(array[0],...,array[n-1])的最大子段和与(array[0],...,array[n/2-1])的最大子段和相同;
b.(array[0],...,array[n-1])的最大子段和与(array[n/2],...,array[n-1])的最大子段和相同;
c.(array[0],...,array[n-1])的最大子段跨过其中间两个元素array[n/2-1]到array[n/2].
?
?
对应a和b两个问题是规模减半的两个相同的子问题,可以用递归求得。
对于c,需要找到以array[n/2-1]结尾的和最大的一段数组和S1=(array[i],...,array[n/2-1])和以array[n/2]开始和最大的一段和S2=(array[n/2],...,array[j]),那么第三种情况的最大值为S1+S2。
?
?
public static int sol3(int[] array, int left, int right) {
if (left == right) {// 只有一个元素
if (array[left] > 0)
return array[left];
else
return 0;
}
int start = left;
int mid = (left + right) / 2;
int end = right;
int MaxLeftBorderSum, MaxRightBorderSum; // 从中间分别到左右两侧的最大连续子序列值,对应c。
int LeftBorderSum, RightBorderSum;
int MaxLeftSum = sol3(array, start, mid);
int MaxRightSum = sol3(array, mid + 1, end);
MaxLeftBorderSum = 0;
LeftBorderSum = 0;
for (int i = mid; i >= left; i--) {
LeftBorderSum += array[i];
if (LeftBorderSum > MaxLeftBorderSum)
MaxLeftBorderSum = LeftBorderSum;
}
MaxRightBorderSum = 0;
RightBorderSum = 0;
for (int i = mid + 1; i <= right; i++) {
RightBorderSum += array[i];
if (RightBorderSum > MaxRightBorderSum)
MaxRightBorderSum = RightBorderSum;
}
int max1 = MaxLeftSum > MaxRightSum ? MaxLeftSum : MaxRightSum;
int max2 = MaxLeftBorderSum + MaxRightBorderSum;
return max1 > max2 ? max1 : max2;
}
?
?
以上已经利用分而治之的方法将复杂度从n^2降低到了nlogn,考虑到还有一种降低复杂度的方法那就是动态规划,所以我们尝试用动态规划的思想去进一步降低复杂度
?
?
动态规划的思想是将一个大问题(N个元素数组)转换为一个较小的问题(n-1个元素数组)。
?
?
我们假设result[0]为已经找到数组[0,1,...n-1]中子数组和最大的,即保存当前找到的最大子数组。sum[i]为包含第i个元素且和最大的连续子数组。对于数组中的第i+1个元素有两种选择:
?
?
a.作为新子数组的第一个元素
b.放入前面已经找到的最大的子数组sum[i-1]中。
?
?
public static int sol4(int[] array) {
int n = array.length;
int[] sum = new int[n];
int[] result = new int[n];
sum[0] = array[0];
result[0] = array[0];
for (int i = 1; i < n; i++) {
sum[i] = max(array[i], array[i] + sum[i - 1]);
// 若A[i]>A[i]+sum[i-1],则作为新子数组的第一个元素
// 否则放入前面已经找到的最大的子数组sum[i-1]中
result[i] = max(sum[i], result[i - 1]);
}
return result[n - 1];
}
?
?
以上的代码其实sum数组和result数组可以只用一个变量就可以了,不用真的用到一个数组,那么将这一点简化,可以得到简化版的最终方法,复杂度为n
?
?
static int sol5(int[] array, int n) {
int sum = array[0];
int result = array[0];
for (int i = 1; i < n; i++) {
sum = max(array[i], array[i] + sum);
result = max(sum, result);
}
return result;
}