leetcode 53.最大子序和(数组、动态规划、分治法)

动态规划

动态规划有点类似于高中学的递推数列。从小范围往大范围计算,在后续计算过程中使用小范围的计算结果来降低时间复杂度。重点在于有一个可以复用之前计算结果的状态转移方程。max = Math.max(max, sum);

本题中,我们从左侧开始遍历,不断增大连续子数组的范围。

  1. 首先我们需要一个记录已经遍历的连续子数组的最大和,初始时max=nums[0]。
    2)要解决的最重要的问题:如何实现复用
    初始时,连续子数组只有数组中的第一个元素,当遍历到第二个元素时:
    如果之前的子数组是负数(和),在本题求最大和时不需要这个负数和的数组前缀,即从当前最后遍历到的元素开始计算。
    如果之前的连续子数组是正数(和),那么判断遍历到的元素:如果是正数就加上;如果是负数,要么保持原来的值去期待负元素后有更大的正数出现,要么加上后 通过一个变量存储当前已遍历的连续子数组的和(显然加上更简易,我们维持两个变量:max记录已遍历连续子数组的最大和,sum已遍历连续子数组的和)。
    3)在每次遍历结束更新已经遍历的连续子数组的最大和max。
class Solution {
    public int maxSubArray(int[] nums) { //
        int max=nums[0];
        int sum=0;
        for(int i=0;i0){     //正数前缀
                sum+=nums[i]; //可能增大也可能减小
            }else{
                sum=nums[i];
            }    
            max = Math.max(max, sum);//但只记住这一轮遍历后的最大值
        }
        return max;
    }
}

时间复杂度 O(n)、空间复杂度 O(n)

分治法
1)分解成子问题
最大子序和在序列左侧;在序列右侧;最大子序列跨越中点。
2)递归求解 即调用自身函数
3)触底合并 即序列内只有一个元素而无法再分解时,自底向上合并子问题解。

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

private int maxSubArrayDivideWithBorder(int[] nums, int start, int end) {
     
    if (start == end) {
     	//分治触底情况
        return nums[start];
    }

    int center = (start + end) / 2;	// 计算中间值
    
	/*计算子序列在整个序列左侧时的序列最大值*/
    int leftMax = maxSubArrayDivideWithBorder(nums, start, center); 
    /*计算子序列在整个序列右侧时的序列最大值*/
    int rightMax = maxSubArrayDivideWithBorder(nums, center + 1, end); 
    /*计算最大子序列横跨中点时的情况*/
    	// 横跨中点时计算包含中点左侧最后一个元素的子序列最大值
    int leftCrossMax = Integer.MIN_VALUE; // 初始化一个值
    int leftCrossSum = 0;
    for (int i = center ; i >= start ; i --) {
     
        leftCrossSum += nums[i];
        leftCrossMax = Math.max(leftCrossSum, leftCrossMax);
    }

    	// 横跨中点时计算包含中点右侧第一个一个元素的子序列最大值
    int rightCrossMax = nums[center+1];
    int rightCrossSum = 0;
    for (int i = center + 1; i <= end ; i ++) {
     
        rightCrossSum += nums[i];
        rightCrossMax = Math.max(rightCrossSum, rightCrossMax);
    }

   		 // 两者加和即为横跨中点时的子序列最大值
    int crossMax = leftCrossMax + rightCrossMax;

    /* 比较三种情况,返回最大值*/
    return Math.max(crossMax, Math.max(leftMax, rightMax));
}

通过中点来不断分割问题,最终情况是序列内只有一个元素,此时开始触底反弹,合并子问题。合并时,最大在子序列在中点左侧、右侧或者跨越中点,取max。

//超出时间限制,下面注释包含了在未学习动态规划时的思考过程
某乎上对动态规划的介绍

class Solution {
     
    public int maxSubArray(int[] nums) {
     
        //两层for循环好像pass
        //为获取下一个正数,中间所经历的负数和应小于所要加的正数??
        //新连续子数组的和应大于新添加的正数,也就是说从新正数开头找子数组不为最大连续子数组,因为前面有相邻正子数组   
        //综上,第二次正数前的负数和+前列正数应大于零(不然直接从第二个开始算不要这个负数前缀) 并且负数和+后续正数应大于零,否则应将前者作为一个较大和连续子数组,并重新开始计算下一个;
        int sumMax=0;
        for(int i=0;i<nums.length;i++){
     
            int sum;//用来计算当前连续子数组的和
            int sum2=0;//用来计算第二个正数前的负数和正数部分
            int sum3=0;//用来计算后续正数和
            while(nums[i]<=0 && i<nums.length)
                i++;  //找到连续子数组的第一个正数
            sum=nums[i];
            while(true){
     
                while(nums[++i]>=0 && i<nums.length){
     
                    sum+=nums[i];//有相连正数情况加和
                }
                while(i<nums.length){
     
                    if(nums[i]<=0 ){
     
                        sum2+=nums[i];  //计算负数部分
                        i++;
                    }
                }
                while(i<nums.length ){
     
                    if(nums[i]>=0 ){
     
                        sum3+=nums[i];//计算后续正数和
                        i++;
                    }
                }
                if((sum2+sum)<0||(sum3+sum2)<0||i<nums.length ) //符合继续为可增长连续子数组情况
                    break;
                else
                    sum=sum+sum3;
            }
            if(sum>sumMax)
                sumMax=sum;       
        }
        return sumMax;
    }
}

复刷的时候再重写一遍博客吧。。。

你可能感兴趣的:(#,java,动态规划,分治算法,leetcode,数组)