53. 比较大子序和(拔剑 Offer 42)

专业知识关键点:二维数组;前缀和;卫兵;动态性整体规划;贪欲;分治算法算法

题目描述

键入一个整形美容二维数组,二维数组中的一个或不断好多个整数金额额度组成一个子二维数组。求所有子二维数组的和的最大值。

要求算法繁杂数为O(n)。

案例
键入: nums = [-2,1,-3,4,-1,2,1,-5,4]
輸出: 6
描述: 不断子二维数组 [4,-1,2,1] 的和比较大,为 6。

玩法一:前缀和 卫兵

不断子二维数组 –> 前缀和
以往往前面分析xml求前缀和,维持两个自自变量,一个是比较大子二维数组和,也就是回应,一个是至少的前缀和,我们可以把这个值掌握为卫兵,这一就是大伙儿用以得到回应的,因为每一次前缀和-这一至少的不容置疑就是比较大的。

class Solution {
    public int maxSubArRay(int[] nums) {
        int pre = 0; //前缀和;
        int minPre = 0; //至少的前缀和:卫兵;
        int maxSum = Integer.MIN_VALUE;
        for(int i = 0; i < nums.length; i  ){
            pre  = nums[i];
            maxSum = Math.max(maxSum, pre-minPre);
            minPre = Math.min(pre, minPre);
        }
        return maxSum;
    }
}

玩法二:贪欲

这题贪欲怎么解?贪哪些?想一下在这个全全过程中,比如-2 1,大伙儿务必-2吗?无需!因为负数只有减少大伙儿最后的和,只起副作用的索性不如不需要了。马上从1慢慢就可以了; 贪的就是负数和一定会减少结果。
因而大伙儿的贪欲选择防范措施就是:只选择和>0的,对于和<=0的都可以放弃了。

class Solution {
    public int maxSubArray(int[] nums) {
        int maxSum = Integer.MIN_VALUE;
        int sum = 0;
        for(int i = 0; i < nums.length; i  ){
            sum  = nums[i];
            maxSum = Math.max(sum, maxSum);
            if(sum <= 0){
                sum = 0; //对于<=0的前缀和,早就没要现实意义了,从下一位置慢慢;
                continue;
            }
        }
        return maxSum;
    }
}

玩法三:分治算法算法

这题可以用分治算法算法去解。希望去求取一个区间[l,r]内的比较大子序和,按照分而治之的意识,可以将其分为左区间和右区间。
左区间L:[l, mid]和右区间R:[mid 1, r].
lSum 说明 [l,r] 内以 l 为左连接点的比较大子段和
rSum 说明 [l,r] 内以 r 为右连接点的比较大子段和
mSum 说明 [l,r] 内的比较大子段和
iSum 说明 [l,r] 的区间和
递归算法算法地求取出L.mSum以及R.mSum之后求取M.mSum。因此最开始在分治算法算法的递归算法算法整个过程中务必维修保养区间比较大不断子列和mSum这一信息。
下边分析如何维修保养M.mSum。大体上有3种很有可能:

  • M上的比较大不断子列和编号编码序列完全在L中,即M.mSum = L.mSum
  • M上的比较大不断子列和开放阅读框完全在R中,即M.mSum = R.mSum
  • M上的比较大不断子列和开放阅读框越过L和R,则该开放阅读框一定是以L中的某一位置慢慢不断到mid(L的右界线),接着从mid 1(R的左界线)慢慢不断到R中的某一位置。因此大伙儿还务必维修保养区间左界线慢慢的比较大不断子列和leftSum以及区间右界线结束的比较大不断子列和rightSum信息
class Solution {
    public class Status{
        public int lSum, rSum, mSum, iSum;
        // lSum 说明 [l,r] 内以 l 为左连接点的比较大子段和
        // rSum 说明 [l,r] 内以 r 为右连接点的比较大子段和
        // mSum 说明 [l,r] 内的比较大子段和
        // iSum 说明 [l,r] 的区间和
        public Status(int lSum, int rSum, int mSum, int iSum){
            tHIS.lSum = lSum;
            this.rSum = rSum;
            this.mSum = mSum;
            this.iSum = iSum;
        }
    }
    public Status getInfo(int[] a, int l, int r){
        if(l == r) return new Status(a[l], a[l], a[l], a[l]); //终止规范;
        int mid = l   ((r-l) >> 1);
        Status lsub = getInfo(a, l, mid);
        Status rsub = getInfo(a, mid 1, r);
        return pushUp(lsub, rsub); 
    }
    //根据两个标识符涵数得到所有开放阅读框结果;
    public Status pushUp(Status l, Status r){
        int iSum = l.iSum   r.iSum;
        int lSum = Math.max(l.lSum, l.iSum r.lSum);
        int rSum = Math.max(r.rSum, r.iSum l.rSum);
        int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum r.lSum);
        return new Status(lSum, rSum, mSum, iSum);
    }

    public int maxSubArray(int[] nums) {
        return getInfo(nums, 0, nums.length-1).mSum;
    }
}

玩法四:动态规划

  • 1.确立dp二维数组和其下合同价寓意:dp[i]说明以i结尾的不断子二维数组的很大和;
  • 2.确立递推公式计算,即状况转移表达式:以i结尾想一下大伙儿有几种很有可能,一种是i-1回家的,也就是上一个的不断子二维数组不断到i处了,那和就为dp[i-1] nums[i],另一种呢,就是本身慢慢,前面那一个不断子二维数组不太好,那就是nums[i]了,想一下为什么前面那一个不太好,还并并不是前面的和会拖累本身,那麼就表示着前面的和是负数;这其实就引过来贪欲的方法 了。可是大伙儿这里不用那麼不方便,立即用一个max函数,取二者大的那一个就可以了;
  • 3.dp校准base case:dp[0]只有一个数,因而dp[0] = nums[0];
class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;
        int[] dp = new int[len]; //以i结尾的不断子二维数组的很大和为dp[i];
        if(nums == null || len <= 1) return nums[0];
        dp[0] = nums[0];
        for(int i = 1; i < len; i  ){
            dp[i] = Math.max(dp[i-1] nums[i], nums[i]); //状况转移;
        }
        //注意我们要解析xml一遍返回比较大的dp;
        int maxSum = dp[0];
        for(int i = 1; i < len; i  ){
            maxSum = Math.max(maxSum, dp[i]);
        }
        return maxSum;
    }
}

当然之上程序流程步骤可以提高,因为大伙儿的dp[i]事实上只和前一状况i-1有关,因而可以采用一个旋转变量来记录,而不用所有二维数组。

class Solution {
    public int maxSubArray(int[] nums) {
        int pre = 0;  //记录前一状况;
        int res = nums[0]; //记录最后结果的最大值;
        for (int num : nums) {
            pre = Math.max(pre   num, num);
            res = Math.max(res, pre);
        }
        return res;
    }
}

体会

这道题目是一道很典型的题目,应用了各种方法 和意识。要常看常做,分治算法算法是在这其中比较困难的,但是要会这种意识。这道题目最好的方法 或者卫兵和动态规划, 事实上贪欲便是以动态规划的一个特殊情况过去的,体会二者的关系;