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]最大子数组只能是两种情况

  1. a[1..j+1]的最大子数组就是 a[1..j] 的最大子数组
  2. 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

  1. 线性解法(https://segmentfault.com/a/1190000000733277)
posted @ 2018-09-06 15:43  nowgood  阅读(182)  评论(0)    收藏  举报