?- LeeCode 53, 最大子序和(Easy)(暴力法, 分治法, 动态规划法)

2.x LeeCode 53, 最大子序和(Easy)(暴力法, 分治法, 动态规划法)

[案例需求]

Category Difficulty Likes Dislikes
algorithms Easy (54.28%) 3276 -
Tags
array | divide-and-conquer | dynamic-programming

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

示例 1:

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

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [0]
输出:0
示例 4:

输入:nums = [-1]
输出:-1

示例 5:

输入:nums = [-100000]
输出:-100000

提示:

1 <= nums.length <= 3 * 104
-105 <= nums[i] <= 105

进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

1.1 解法一: 暴力法

[思路分析]
由上面案例需求可知, 此题目的是让我们求出连续数组中任意一个连续的数字组合, 并且这个连续的数字组合的相加之和是所有数字组合中
最大的.

我们可以很容易想到双重遍历数组进行暴力求解; 外层循环我们从0–>nums.length, 目的是遍历每一个索引i; 内层循环我们从 i–>nums.length, 在每一i下的内循环中, 我们从i开始直到数组末尾, 每一次相加都是以nums[i]为开头对应的连续子串的组合, 所以利用这个方法, 我们可以通过记录最大和sumMax来循序渐进的找出最大的子序和;

[代码实现]

public class Solution {
    
    
    //暴力解法
    /**
     * 1. 遍历待排数组, 遍历一个就加一次, 把这个和保存在变量maxSum中,
     * 2. 每一次加法都要与maxSum比较, 大的话就更新这个变量的值, 小于或等于就不变
     * 3. 等到遍历完毕后, 返回这个maxSum
     */
    public static int maxSubArray(int[] nums) {
    
    
        int len = nums.length;
        int sum;
        //剪枝
        if(len == 1) return nums[0];

        //双重循环, 外层循环控制每个相加子串的开始位置. 比如 i=2, 那么子串就从2往后, 不断的增长到n, 途中进行加操作
        //        内层循环, 从i开始往后一个一个的相加, 每加上一个数, 就与之前的加结果进行比较
        int maxSum = Integer.MIN_VALUE;
        for(int i=0; i < len; i++){
    
    
            sum = 0;
            for(int j=i; j < len; j++){
    
    
                 sum = sum + nums[j];
                 maxSum = Math.max(sum, maxSum);
            }
        }
        return maxSum;
    }

    public static void main(String[] args) {
    
    
        int[] nums = {
    
    -2,1,-3,4,-1,2,1,-5,4};
        System.out.println(maxSubArray(nums));
    }
}

1.2 解法二: 分治法

[思路分析]

分治法就是将整个数组切分为几个小组, 每个小组然后再切分为几个更小的小组, 一直到不能继续切分也就是只剩一下一个数字为止. 然后每个小组会计算出最优值, 汇报给上一级的小组, 一级一级汇报, 上级拿到下级的汇报找到最大值, 得到最终的结果. 和合并排序的算法类似, 先切分, 再合并结果;

这个问题的关键就是如何切分这些组合才能使每个小组之间不会有重复的组合(有重复的组合意味着有重复的计算量.)

首先是切分分组方法, 举例: [-2, 1, -3, 4, -1, 2, 1, -5, 4], 一共有9个元素, 我们首先中值切分 mid = (start+end)/2, 得到中间元素的索引为4, 也就是-1, 切分后我们会得到三个组合:

  • [-2, 1, -3, 4, -1] 以及他们的子序列(在中值-1的左边并且包含中值的为一组);
  • [2, 1, -5, 4]以及他们的子序列(在中值-1的右边且不包含它的为一组);
  • 我们的连续子序列还可能是跨中值的组合, 比如-1的左边有几个值, 右边也有几个值, 组合起来的最大子序和是最大的. 所以我们需要把任何包含中值-1以及他右边元素2的序列为一组(换言之就是包含左边序列的最右边元素以及右边序列最左边元素(就是nums[mid] 和 nums[mid+1])的序列为一组), 比如: [4, -1, 2, 1]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dR50PmA2-1625128433553)(2021-06-04-11-08-25.png)]


[实现方案]

以上的三个组合内的序列没有任何的重复的部分,而且一起构成所有子序列的全集,计算出这个三个子集合的最大值,然后取其中的最大值,就是这个问题的答案了。

然而前两个子组合可以用递归来解决,一个函数就搞定,第三个跨中心的组合应该怎么计算最大值呢?

答案就是先计算左边序列里面的包含最右边元素的子序列的最大值,也就是从左边序列的最右边元素向左一个一个累加起来,找出累加过程中每次累加的最大值,就是左边序列的最大值。

同理找出右边序列的最大值,就得到了右边子序列的最大值。左右两边的最大值相加,就是包含这两个元素的子序列的最大值。

在计算过程中,累加和比较的过程是关键操作,一个长度为 n 的数组在递归的每一层都会进行 n 次操作,分治法的递归层级在 logNlogN 级别,所以整体的时间复杂度是 O(nlogn)O(nlogn),在时间效率上不如动态规划的 O(n)O(n) 复杂度。

[代码实现]

package LeetCodeDemo.lt53;
//分治法求最大子序和
public class Solution_A {
    
    
    // 分为了两段, (start--> mid) 和 (mid+1--> end)
    public static int maxSubArray(int[] nums) {
    
    
        return maxSubArrayWithBorder(0, nums.length-1, nums);
    }

    public static int maxSubArrayWithBorder(int start, int end, int[] nums){
    
    
        //递归出口
        if(start == end) return nums[start];

        int mid = (start + end)/2;
        //分
        int leftMax = maxSubArrayWithBorder(start, mid, nums);
        int rightMax = maxSubArrayWithBorder(mid+1,  end, nums);

        //治(计算)
        //1. 计算左半部分的和(从start--> mid)
//        int leftMaxSum = nums[start];
//        int sumA = 0;
//        for(int i=start; i <= mid; i++){
    
    
//            sumA = sumA + nums[i];
//            leftMaxSum = Math.max(sumA, leftMaxSum);
//        }
        int leftMaxSum = nums[mid];
        int sumA = 0;
        for(int i=mid; i>=start; i--){
    
    
            sumA = sumA + nums[i];
            leftMaxSum = Math.max(sumA, leftMaxSum);
        }
        //2. 计算右半部分的和(从mid+1--> end)
        int rightMaxSum = nums[mid+1];
        int sumB = 0;
        for(int j=mid+1; j <= end; j++){
    
    
           sumB = sumB + nums[j];
           rightMaxSum = Math.max(sumB, rightMaxSum);
        }

        //3. 计算跨越中值的和
        int crossSum = leftMaxSum + rightMaxSum;

        //递归完成后, 我们每一次的左半部分的和, 有半部分的和, 跨越中值的和都会在这里比较
        return Math.max(crossSum, Math.max(leftMax, rightMax));
    }

    public static void main(String[] args) {
    
    
        int[] nums = {
    
    -2,1,-3,4,-1,2,1,-5,4};
        System.out.println(maxSubArray(nums));
    }

}

在这里插入图片描述

对上面代码的概述:

在这里插入图片描述

1.3 解法三: 动态规划法

おすすめ

転載: blog.csdn.net/nmsLLCSDN/article/details/118389927