leetcode53 最大连续子数组
线性时间解法
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int max_val = INT_MIN;
int cur_max = 0;
for (int i = 0; i < n; i++) {
cur_max += nums[i];
max_val = max(max_val, cur_max);
cur_max = max(cur_max, 0);
}
return max_val;
}
};
线性时间解法的思路
如果 a[1..j] 已经得到了其最大子数组,那么a[1..j+1]最大子数组只能是两种情况
- a[1..j+1]的最大子数组就是 a[1..j] 的最大子数组
- a[1..j+1]的最大子数组是 a[i..j+1]
那么,如何求得所谓的 2
中的 a[i..j+1]呢?
首先需要承认这样的事实
如果一个数组 a[p..r] 求和得到负值,
那么数组 a[p..r+1] 的最大子数组肯定不会是 a[p..r+1],
因为 a[p..r]+a[r+1] < a[r+1].
在代码中,我们用 cur_max 存储 a[p..r] 的和,
只要 a[p..r] 的求和是负值,那么从下一个a[r+1] 值开始,cur_max 重新从 0 开始求和,
只要 cur_max > max_val, 就更新 max_val, 一次遍历后,我们就能得到最大连续子数组的和,
接下来,我们证明该算法是有效性
证明
对于所有数组元素,存在这样的元素对数组进行划分,
如果加上该元素之前 cur_max >= 0 且cur_max+a[i] < 0,那么该元素 a[i] 是一个划分边界,
这样,数组会形成好多段区间,每段结束元素都满足 cur_max >= 0 且 cur_max+a[i] < 0.
所以我们能得到多个划分块 a[p..q],每个划分快的和是负值,
划分块有这样的性质,对任意 p <= i < q, 显然,sum(a[p..i]) >= 0 且sum(a[i..q])< 0;
递归解法
递归解法思路见算法导论4.1节
package chapter4;
import java.util.Arrays;
/**
* Created by wangbin on 2017/3/1.
* 时间复杂度 : O(nlgn)
*/
public class Exercise414 {
public static int[] RecursiveFindMaxSubArray(int a[]) {
a = pretreatment(a);
int b[] = findLeftRightSubArray(a, 0, a.length - 1);
if (b[2] == 0) { //用来处理没有最大子数组的问题
return new int[0];
}
return new int[] {b[0] -1, b[1], b[2]}; //处理购入的时间点
}
private static int[] pretreatment(int a[]) {
for (int i = a.length - 1; i > 0; i--) {
a[i] = a[i] - a[i-1];
}
a[0] = 0;
return a;
}
private static int[] findLeftRightSubArray(int[] a, int low, int high) {
int mid = (low + high) / 2;
if(low == high) {
return new int[]{low, high, a[low]};
}
int[] ansLeft = findLeftRightSubArray(a, low, mid);
int[] ansRight = findLeftRightSubArray(a, mid + 1, high);
int[] ansMid = findMaxCrossingSubArray(a, low, high);
if (ansLeft[2] >= ansRight[2] && ansLeft[2] >= ansMid[2]) {
return ansLeft;
} else if (ansRight[2] >= ansLeft[2] && ansRight[2] >= ansMid[2]) {
return ansRight;
} else {
return ansMid;
}
}
private static int[] findMaxCrossingSubArray(int a[], int low, int high) {
int mid = (low + high) / 2;
int leftSum, rightSum, left, right;
left = right = mid;
leftSum = -Integer.MAX_VALUE;
rightSum = -Integer.MAX_VALUE;
int sum = 0;
for(int i = mid; i >= 0; i--) {
sum = sum + a[i];
if (sum > leftSum) {
leftSum = sum;
left = i;
}
}
sum = 0; //detail
for(int i = mid + 1; i <= high; i++) { // 从 mid + 1 而不是 mid 开始
sum = sum + a[i];
if (sum > rightSum) {
rightSum = sum;
right = i;
}
}
return new int[]{left, right, rightSum + leftSum} ;
}
}
reference