分治法——53. Maximum Subarray

使用分治法需满足的条件

  1. 原问题可以分解为若干个规模较小的子问题
  2. 子问题互相独立
  3. 子问题的解合并处理后可得到原问题的解

LeetCode-53:Maximum subarray

给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

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

普通O(n)

暴力求解的话,第一层是n个元素的循环,第二层是每个元素与其后面的所有元素之和,记录最大值。共计算 n n *n2 次,时间复杂度O(n 2 2 )

思路

借鉴自discuss
观察可发现,当第 Xi X i 数的值大于 X0 X 0 Xi1 X i − 1 之和时,就应该抛弃 X0 X 0 Xi1 X i − 1 d的和(但是不代表该sum值不是最大值,只是开始新的计算);反之, Xi X i 的值由 X0 X 0 X1 X − 1 之和代替

比如说:[-2,1,-3,4,-1,2,1,-5,4]

  • -2+1=-1<1,所以没必要参与接下来的计算。 X1 X 1 的值还是1;
  • 1+-3=-2>-3。替换 X2 X 2 的值为-2;
  • -2+4=2<4,故 X3 X 3 的值还是4;
  • 。。。。。。计算出最大值为6;
  • 由于不需要求出子序列是什么,只要求结果,故可使用该时间复杂度为O(n)的算法。

白纸代码

这里写图片描述

Ac代码

【暂未使用Junit写单元测试】

public class MaxSumSubArray {

    public static int maxSumValueSubArray(int[] nums) {
        int max = nums[0];
        if (nums.length == 1) {
            return max;
        }
        for (int i = 0; i < nums.length - 1; i++) {
            nums[i + 1] = Math.max(nums[i + 1], nums[i] + nums[i + 1]);
            max = Math.max(max, nums[i + 1]);
        }
        return max;
    }

    public static void main(String[] args) {
        int[] nums1 = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
        int[] nums2 = {-5, 4};
        int[] nums3 = {6, -2, 0};
        System.out.println(MaxSumSubArray.maxSumValueSubArray(nums3));
    }
}

优于82.72%的提交结果

分治法求解

思路

  1. 将原问题分解为众多子问题,子问题求的目标与总问题相同,那么最终的解一定可以由这些子问题中解推演得到
  2. 针对此题,当子问题划分到只有两个数字,比如6, -3。那么,答案是6(左半部分值)、-3(右半部分值)和6+(-3)=3(两部分之和)这三种值中的最大值
  3. 根据2,可递推得:最大值是左半部分最大值、右半部分最大值、左半部分的后缀最大值与右半部分的前缀最大值之和这三个值中的最大值
  4. 因为每次问题变为上一次的一半,所以总共分解 log 2 2 n 次。

稿纸示意图

这里写图片描述

白纸测试代码

这里写图片描述
注:@test改为@Test

public class MaxSumSubArrayDivideTest {
    @Test
    public void testMaxSubArrayDivide_1() throws Exception {
        MaxSumSubArrayDivide maxSumSubArrayDivide = new MaxSumSubArrayDivide();
        int[] nums = {-3};
        int expected = nums[0];
        assertEquals("Wrong!Missmatch!", expected, maxSumSubArrayDivide.maxSubArray(nums));
    }
    @Test
    public void testMaxSubArrayDivide_2() throws Exception {
        MaxSumSubArrayDivide maxSumSubArrayDivide = new MaxSumSubArrayDivide();
        int[] nums = {-2,1,-3,4,-1,2,1,-5,4};
        int expected = 6;
        assertEquals("Wrong!Missmatch!", expected, maxSumSubArrayDivide.maxSubArray(nums));
    }
} 

白纸代码

这里写图片描述

Ac代码

public class MaxSumSubArrayDivide {
    public int maxSubArray(int[] nums) {
        if (nums.length == 1) {
            return nums[0];
        } else return maxSubArrayDivide(nums, 0, nums.length - 1);
    }

    private int maxSubArrayDivide(int[] nums, int from, int to) {
        // 递归出口
        if (from == to) {
            return nums[from];
        }
        int middle = (from + to) / 2;
        // left recursive call
        int left = maxSubArrayDivide(nums, from, middle);
        // right recursive call
        int right = maxSubArrayDivide(nums, middle + 1, to);

        // 开始O(n)时间复杂度的计算,"叶子"返回后,需要计算第三种可能:左半部分的后缀+右半部分的前缀之和为最大值
        int leftEnd = nums[middle], leftSuffix = leftEnd;
        int rightBegin = nums[middle + 1], rightPrefix = rightBegin, i = 1;
        // 计算左半部分的最大后缀
        while (middle - i >= from) {
            leftEnd += nums[middle - i++];
            leftSuffix = Math.max(leftSuffix, leftEnd);
        }
        // 计算右半部分的最大前缀
        i = 2;
        while (middle + i <= to) {
            rightBegin += nums[middle + i++];
            rightPrefix = Math.max(rightPrefix, rightBegin);
        }
        int join = leftSuffix + rightPrefix;
        return Math.max(left, Math.max(right, join));
    }
}

分治算法复杂度求解

这里写图片描述
所以该实现比之前的O(n)要差一些。
“击败“13%的AC提交。。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值