一道题让你掌握5中思路:暴力+动态规划+贪心+分治

剑指 Offer 42. 连续子数组的最大和

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

要求时间复杂度为O(n)。

示例1:

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

来源:力扣(LeetCode)
链接:点击跳转https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof


暴力求解1

    //暴力求解,时间复杂度On3
    public int maxSubArray3(int[] nums) {
        int max = nums[0];
        //i来记录连续i+1个数字
        for(int i = 0; i < nums.length; i++) {
            //j来记录每次遍历i+1个连续数的次数
            for(int j = 0; j < nums.length-i; j++) {
                int sum = 0;
                //k来求区段的和
                for(int k = j; k <= j+i; k++) {
                    sum = sum+nums[k];
                }
                //算完区段的和才判断,可用方法二简化
                if(sum > max) {
                    max = sum;
                }
            }
        }
        return max;
    }

暴力求解2

public int maxSubArray2(int[] nums) {
        int max = nums[0];
        //i来记录每次从第i个数开始往后算
        for(int i = 0; i < nums.length; i++) {
            int sum = 0;
            //j来记录每次从i开始往后一直加
            for(int j = i; j < nums.length; j++) {
                sum = sum+nums[j];
                //每加一次判断一次
                if(sum > max) {
                    max = sum;
                }
            }
        }
        return max;
    }

动态规划1

  • 状态定义: 设动态规划列表 dp ,dp[i]代表以元素 nums[i]为结尾的连续子数组最大和。
    为何定义最大和 dp[i] 中必须包含元素 nums[i]:
    保证 dp[i]递推到dp[i+1]的正确性;如果不包含 nums[i] ,递推时则不满足题目的 连续子数组 要求。
  • 转移方程: 若 dp[i-1] ≤0 ,说明 dp[i - 1] 对 dp[i]产生负贡献,即 dp[i-1] + nums[i]还不如 nums[i] 本身大。
    当 dp[i - 1] > 0时:执行 dp[i] = dp[i-1] + nums[i] ;
    当 dp[i - 1]≤0 时:执行 dp[i] = nums[i];
  • 初始状态: dp[0] = nums[0],即以 nums[0]结尾的连续子数组最大和为 nums[0] 。
  • 返回值: 返回 dp列表中的最大值,代表全局最大值。

    //动态规划
    public int maxSubArray1(int[] nums) {
        int max = nums[0];
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        for(int i = 1; i < nums.length; i++) {
            if(dp[i-1] < 0) {
                dp[i] = nums[i];
            }else {
                dp[i] = dp[i-1] + nums[i];
            }
            if(dp[i] > max) {
                max = dp[i];
            }
        }
        return max;
    }

动态规划2

  • 状态转移方程:dp[i] = max(dp[i-1] + nums[i], nums[i]),其中dp[i]表示以索引i为结束点基准的子数组的最大值。
  • 初始状态:dp[0]= nums[0]。
    //动态规划
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int max = dp[0];
        for(int i=1;i < nums.length;i++){
            dp[i] = Math.max(dp[i-1] + nums[i],nums[i]);
            max = Math.max(max,dp[i]);
        }
        return max;
    }


贪心算法

只要当前子段的和最大,就记录到res中,如果sum的结果小于0,必须将sum = 0,然后重新开始计算新的子段和,因为加上负数只会更小

    //贪心算法
    public int maxSubArray5(int[] nums) {
        int res = nums[0];
        int sum = 0;
        for(int n : nums)
        {
            sum += n;
            if(sum > res)   res = sum;
            if(sum < 0)     sum = 0;
        }
        return res;
    }

分治思想

分治法的做题思路是:先将问题分解为子问题;解决子问题后,再将子问题合并,解决主问题。

使用分治法解本题的思路是:

  • 将数组分为 2 部分。例如 [1, 2, 3, 4] 被分为 [1, 2] 和 [3, 4]
  • 通过递归计算,得到左右两部分的最大子序列和是 lsum,rsum
  • 从数组中间开始向两边计算最大子序列和 cross
  • 返回 max(lsum, cross, rsum)

在这里插入图片描述

    //分治
    public int maxSubArray6(int[] nums) {
        return divideConquer(nums,0,nums.length-1);
    }
    public int divideConquer(int[] nums,int low,int high) {
        //递归出口
        if (low>=high){
            return nums[low];
        }
        //三种情况,第一种:数组在左区间
        //第二种:数组在又区间
        //第三种:数组横跨左右区间
        //前两种递归求解即可
        //第三种情况以中间位置向两周扩散,找到最大值
        int mid = low+(high-low)/2;
        int left = divideConquer(nums,low,mid-1);
        int right = divideConquer(nums,mid+1,high);
        int maxMid = nums[mid];
        int curMId = nums[mid];
        for (int i = mid-1;i>=low;i--){
            curMId += nums[i];
            if (maxMid<curMId) maxMid = curMId;
        }
        curMId = maxMid;
        for (int i = mid+1;i<=high;i++){
            curMId += nums[i];
            if (maxMid<curMId) maxMid = curMId;
        }
        //4.返回三种情况中的最大值
        return Math.max(Math.max(left,right),maxMid);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值