代码随想录训练营第二十七天 | 455.分发饼干 376. 摆动序列 53. 最大子序和

贪心

贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
最好用的策略就是举反例。
做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了。

455.分发饼干

大饼干优先喂给胃口大的小孩儿。

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);

        int count = 0;

        int i=g.length-1, j=s.length-1;
        for(; i>=0 && j>=0; i--){
            if(g[i]>s[j]) continue;
            count++;
            j--;
        }
        return count;
    }
}

376. 摆动序列 (情况很复杂)

整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。

本题要考虑三种情况:

  • 上下坡中有平坡:连续的相同元素,只计算最后一个 —— 相同数字连续时, prediff = 0 ,curdiff < 0 或 >0 也记为波谷。
  • 数组首尾两端:假设数组最前面还有一个数字 prediff=0 。序列默认序列最右边有一个峰值,初始值为1
  • 单调坡中有平坡:不要实时更新 prediff,只需要在 这个坡度 摆动变化的时候,更新 prediff 就行,这样 prediff 在 单调区间有平坡的时候 就不会发生变化,造成我们的误判。

指针:使用prediff, curdiff,而非 pre, cur, next

// 一开始的思路:是错的
class Solution {
    public int wiggleMaxLength(int[] nums) { // 波峰越多,最长摆动序列越长 
        if(nums.length==1) return 1;
        else if(nums.length==2){
            if(nums[0] != nums[1]) return 2;
            else return 1;
        }

        int count = 0;
        int left=0, cur=1, right=2;
        for(; right<nums.length;){
            if((nums[cur] - nums[left]) * (nums[right] - nums[cur])<0 ) count++;

            right++;
            cur++;
            left++;
        }

        // 两边……
        if(nums[0] == nums[right-1]) count+=1;
        else count+=2;

        return count;
    }
}

此题考虑条件很多,不太好想……

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int result = 1; // 默认最右边也是序列
        int prediff = 0, curdiff = 0;

        for(int i=1; i<nums.length; i++){
            curdiff = nums[i] - nums[i-1];

            if((prediff<=0 && curdiff>0) || (prediff>=0 && curdiff<0)){
                result++;
                prediff = curdiff; // 更新pre
            }
        }
        return result;
    }
}

动态规划

下标及其含义:right是作波峰还是波谷——总之要算进来
dp及其下标的值,一定与题解有关:对于我们当前考虑的这个数,要么是作为山峰(即 nums[i] > nums[i-1]),要么是作为山谷(即 nums[i] < nums[i - 1])。

  • 设 dp 状态dp[i][0],表示考虑前 i 个数,第 i 个数作为山峰的摆动子序列的最长长度
  • 设 dp 状态dp[i][1],表示考虑前 i 个数,第 i 个数作为山谷的摆动子序列的最长长度

递推公式

  • dp[i][0] = max(dp[i][0], dp[j][1] + 1),其中0 < j < inums[j] < nums[i],表示将 nums[i]接到前面某个山谷后面,作为山峰。
  • dp[i][1] = max(dp[i][1], dp[j][0] + 1),其中0 < j < inums[j] > nums[i],表示将 nums[i]接到前面某个山峰后面,作为山谷。

初始状态
由于一个数可以接到前面的某个数后面,也可以以自身为子序列的起点,所以初始状态为:dp[0][0] = dp[0][1] = 1

还可以这样写:dp[0][0] = dp[0][1] = 1;

// DP
class Solution {
    public int wiggleMaxLength(int[] nums) {
        // 0 i 作为波峰的最大长度
        // 1 i 作为波谷的最大长度
        int dp[][] = new int[nums.length][2];

        dp[0][0] = dp[0][1] = 1; // 可以以自身为子序列的起点
        for (int i = 1; i < nums.length; i++){
            //i 自己可以成为波峰或者波谷
            dp[i][0] = dp[i][1] = 1; // 可以以自身为子序列的起点

            for (int j = 0; j < i; j++){
                if (nums[j] > nums[i]){ // 是否可以作为新的波谷:遍历前面所有的波谷
                    // i 是波谷
                    dp[i][1] = Math.max(dp[i][1], dp[j][0] + 1);
                }
                if (nums[j] < nums[i]){ // 是否可以作为新的波峰:遍历前面所有的波峰
                    // i 是波峰
                    dp[i][0] = Math.max(dp[i][0], dp[j][1] + 1);
                }
                // 如果相等,证明什么也做不到,既不是波峰,也不是波谷
            }
        }

        return Math.max(dp[nums.length - 1][0], dp[nums.length - 1][1]);
    }
}

53. 最大子序和

只能想到两端一定要是正数——但是如果没有正数……

总之,还是要看随想录的解法:

局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。

class Solution {
    public int maxSubArray(int[] nums) {
        int result = Integer.MIN_VALUE;
        int left=0, right=1; // 左闭右开
        int count = nums[0];
        while(right<=nums.length){
            result = result>count? result: count;
            if(count>0){
                if(right<nums.length) count+= nums[right];
                
            }else{
                left=right;
                if(right<nums.length) count = nums[left];
            }
            right++;
        }
        return result;
    }
}

代码随想录解法:

  1. 不需要用到区间:因为一旦count<0时,区间大小就要缩小为0。所以,对right进行遍历即可。
  2. 使用Math.max,替代三元表达式。
class Solution {
    public int maxSubArray(int[] nums) {
        if (nums.length == 1){ // 边缘条件
            return nums[0];
        }
        int sum = Integer.MIN_VALUE;
        int count = 0;
        for (int i = 0; i < nums.length; i++){
            count += nums[i];
            sum = Math.max(sum, count); // 取区间累计的最大值(相当于不断确定最大子序终止位置)
            if (count <= 0){
                count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
            }
        }
       return sum;
    }
}

动态规划

class Solution {
    public int maxSubArray(int[] nums) {
        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];
            }
        }
        return Arrays.stream(dp).max().getAsInt();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值