代码随想录算法训练营第31天 || 理论基础 ||455.分发饼干 || 376. 摆动序列 || 53. 最大子序和

代码随想录算法训练营第31天 || 理论基础 ||455.分发饼干 || 376. 摆动序列 || 53. 最大子序和

理论基础

什么是贪心?

本质就是选择每一阶段最优,从而达到全局最优。例如取n张钞票获得最大值,每次就取最大面额。

贪心的套路(什么时候使用贪心)

贪心算法没有固定的套路,贪心也不需要去严格证明,手动模拟一下感觉可以局部最优推出全局最优即可(想不到反例)

贪心一般解题步骤(四步)

  • 将问题氛围若干个子问题
  • 找出合适的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解

其实平常很难真正按照这四步去思考,做题只需要想清楚局部最优,能推出全局最优就行了。

455.分发饼干

题目介绍:

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

示例 1:

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。

示例 2:

输入: g = [1,2], s = [1,2,3]
输出: 2
解释: 
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

个人思路:

对两个数组进行排序,然后双指针从小开始逐个分配饼干即可

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int resutl = 0;
        for (int i = 0, j = 0; i < g.length && j < s.length; ) {
            if (g[i] <= s[j]) {
                i++;
                j++;
                resutl++;
            } else j++;
        }
        return resutl;
    }
}

题解思路:

题解和上述思路差不多,不过它是用尽可能大的饼干满足胃口较大的孩子,而上述思路是用尽可能小的饼干满足胃口较小的孩子

376. 摆动序列

题目介绍:

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 **摆动序列 。**第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

  • 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
  • 相反,[1, 4, 7, 2, 5][1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列最长子序列的长度

示例 1:

输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。

示例 2:

输入:nums = [1,17,5,10,13,15,10,5,16,8]
输出:7
解释:这个序列包含几个长度为 7 摆动序列。
其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。

示例 3:

输入:nums = [1,2,3,4,5,6,7,8,9]
输出:2

这道题大致想了一下,没有思路,个人想到使用递归回溯,但时间复杂度可能比较大

题解解析:

本题可以先通过话趋势图来构思

376.摆动序列

局部最优:删除单调坡上的节点(不包括单调坡两端的节点),那么这个坡度就可以有两个局部峰值

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

实际上,我们并不需要删除,只需要记录长度即可。判断峰值可用 前差值 * 后差值 < 0统计。prediff < 0 && curdiff > 0 或者 prediff > 0 && curdiff < 0条件

本题要考虑三种特殊情况:

  1. 上下坡中有平坡
  2. 数组首尾两端
  3. 单调坡中有平坡

上下坡中有平坡

代码随想录算法训练营第31天 || 理论基础 ||455.分发饼干 || 376. 摆动序列 || 53. 最大子序和_第1张图片

如果我们要删除的是左边两个2,我们注意到第一个2处,prediff > 0 && curdiff = 0,而最后一个2处,prediff = 0 && curdiff < 0,所以,我们可以将后者条件作为收录峰值的条件之一。可得:(preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)

数组首尾两端

我们发现我们的条件需要至少三个元素才可以判断,如果只有两个元素该怎么办呢?

法一:可以在首元素前面假设有一个和首元素一样的元素,使得一开始的prediff = 0,这样再根据此前的条件判断就可以统计峰值了,但是我们还发现其实相比结果还是差1,所以我们一开始就把最右端看作一个峰值,默认初始化result = 1起。(这种写法较好,可以使代码较简略,统一整个代码逻辑)

法二:直接通过if判断写死。(其他情况不能直接初始化result = 2,可能会有一路平坡的样例,得初始化result = 1,最终result>1result++

此时可得到以下代码,但是通不过。

// 版本一
class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        int curDiff = 0; // 当前一对差值
        int preDiff = 0; // 前一对差值
        int result = 1;  // 记录峰值个数,序列默认序列最右边有一个峰值
        for (int i = 0; i < nums.size() - 1; i++) {
            curDiff = nums[i + 1] - nums[i];
            // 出现峰值
            if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
                result++;
            }
            preDiff = curDiff;
        }
        return result;
    }
};

我们还得考虑情况三

单调有平坡

代码随想录算法训练营第31天 || 理论基础 ||455.分发饼干 || 376. 摆动序列 || 53. 最大子序和_第2张图片

出现这种问题的原因是我们实时更新prediff,其实并没有必要,可以发现实时更新会在最后一个2处出现错误,我们应该在坡峰发生变化的时候改变prediff,其他时候保持之前的状态就好(

此前遇到过非破峰的情况:

  • 一直单调:没啥影响,prediff没变
  • 遇到平坡:有影响,后面出现单调坡就不知道前面的情况了

所以我们需要小改代码即可

//版本2
class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        int curDiff = 0; // 当前一对差值
        int preDiff = 0; // 前一对差值
        int result = 1;  // 记录峰值个数,序列默认序列最右边有一个峰值
        for (int i = 0; i < nums.size() - 1; i++) {
            curDiff = nums[i + 1] - nums[i];
            // 出现峰值
            if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
                result++;
                preDiff = curDiff; // 注意这里,只在摆动变化的时候更新prediff 
            }
        }
        return result;
    }
};

Java版本:代码看起来挺简单,实际逻辑还是值得细品

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int prediff = 0;
        int curdiff = 0;
        int result = 1;
        for (int i = 0; i < nums.length - 1; i++) {
            curdiff = nums[i + 1] - nums[i];
            if (prediff <= 0 && curdiff > 0 || prediff >= 0 && curdiff < 0) {
                result++;
                prediff = curdiff;
            }
        }
        return result;
    }
}

53. 最大子序和

题目介绍:

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

子数组 是数组中的一个连续部分。

示例 1:

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

示例 2:

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

示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

个人思路:

使用以前学过的双指针法,类似滑动模块思路解决。

关键逻辑,sum < 0时,重新确定新区间,一路遍历一路比较sum和result即可

class Solution {

    public int maxSubArray(int[] nums) {
        int slow = 0;
        int fast = 0;
        int sum = 0;
        int result = Integer.MIN_VALUE;
        while (fast < nums.length) {
            if (sum < 0) {//sum<0,必须砍掉才能找到更大区间和
                slow = fast++;
                sum = nums[slow];
            } else sum += nums[fast++];
            if (sum > result)
                result = sum;
        }
        return result;
    }
}

ast < nums.length) {
if (sum < 0) {//sum<0,必须砍掉才能找到更大区间和
slow = fast++;
sum = nums[slow];
} else sum += nums[fast++];
if (sum > result)
result = sum;
}
return result;
}
}


你可能感兴趣的:(算法,贪心算法,leetcode)