最大字段和问题

最大子段和 -- 分治,dp

1.问题描述

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

2.分析问题

2.1 方案1

此题很显然可以用多层for循环解决, 时间复杂度为O(n^2) .

2.2 方案2, 图像压缩问题的变形

  • 采用dp的思路, 我们把这个数组分成不同的段, **dp[i] 表示以nums[i]结尾的那个段的最大字段和, 所以就存在nums[i]这个元素是加入前一段中, 还是自成一段这就是需要思考的问题, 如果dp[i-1] < 0, 如果nums[i]加入前一段中, 最大字段和为dp[i-1] + nums[i], 这个值必然小于nums[i], 因为dp[i-1]是负数, 负数加上一个数,必然比这个数小, 就像是一个数减了某个数, 肯定变小. 其他情况下只用求dp[i-1] + nums[i] 和 nums[i] 的最大值即可 **

  • 通过上面的分析, 很容易写出状态转移方程

// dp初始化
dp[0] = nums[0]
if(dp[i-1] < 0) dp[i] = nums[i]; // 自成一段
if(dp[i-1] >= 0) dp[i] = Max(dp[i-1] + nums[i], nums[i])
  • 简化转移方程, dp[i-1]<0时, nums[i] + dp[i-1] 必然是小于nums[i]的, 其实也是在求Max(dp[i-1] + nums[i], nums[i]),所以这个题只用一个方程就搞定. 简化了if else的判断, 对程序性能提升也是有帮助的
dp[0] = nums[0]
dp[i] = Max(dp[i-1] + nums[i], nums[i])   
  • 这个题很有个很坑的地方就是, dp中最后一个元素并不是最终要求的结果, 这个我们平时做的题有很大的出入, dp[i]的含义是以nums[i]结尾的那个段的最大字段和, 那么dp中最后一个元素表示的是以nums中最后一个元素结尾的那个段的最大字段和, 最大的字段和不一定以nums中最后一个元素结尾,所以要最终要求的目标是dp数组中的最大值

    public int maxSubArray(int[] nums) {
            if(nums == null || nums.length <= 0) throw new IllegalArgumentException();
            int[] dp = new int[nums.length];
            dp[0] = nums[0];
            for(int i = 1; i < nums.length; ++i){
                dp[i] = Math.max(nums[i], nums[i] + dp[i-1]);
            }
            // return dp[nums.length-1]; 神坑
            int maxValue = Integer.MIN_VALUE;
            for(int j = 0; j < dp.length; ++j){
                if(dp[j] > maxValue)maxValue = dp[j];
            }
            return maxValue;
    

3. 方案3

  • 采用分治的思路, 我们把数组从中间分开, 最大子序列的位置就存在以下三种情况

    • 最大子序列在左半边, 采用递归解决
    • 最大子序列在右半边, 采用递归解决
    • 最大子序列横跨左右半边, 左边的最大值加上右边的最大值
  • 时间复杂度分析

T(n) = 2 F(n/2) + n

时间复杂度O(nlgn)

public int maxSubArray(int[] nums) {
        if(nums == null || nums.length <= 0) throw new IllegalArgumentException();
        return  helper(nums, 0, nums.length-1);

    }

    private int helper(int []  nums, int start, int end){
        if(nums == null || nums.length <= 0) throw new IllegalArgumentException();

        if(start == end)return nums[start];

        
        int middle = start + (end - start) / 2;
        int leftSums = helper(nums,start, middle);
        int rightSums = helper(nums,middle+1, end);
        
        // 横跨左右两边
        int leftRightSums;

        // 左边的最大值
        int lsums = Integer.MIN_VALUE, temp = 0;
        for(int i = middle; i >= start; i--){
               temp += nums[i];
               if(temp > lsums)lsums = temp;
        }

        // 右边的最大值
        int rsums = Integer.MIN_VALUE;
        temp = 0;
        for(int j = middle+1; j <= end; j++){
            temp += nums[j];
            if(temp > rsums) rsums = temp;
        }
        leftRightSums = rsums + lsums;

        return Math.max(Math.max(leftSums, rightSums), leftRightSums);
    }


posted @ 2020-11-07 20:59  FizzPu  阅读(290)  评论(0编辑  收藏  举报