代码随想录刷题笔记 DAY 30 | 分发饼干 No.455 | 摆动序列 No.376 | 最大子数组和 No.53

Day 31

01. 分发饼干(No. 455)

题目链接

代码随想录题解

1.1 题目

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

对每个孩子 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.

提示:

  • 1 <= g.length <= 3 * 104
  • 0 <= s.length <= 3 * 104
  • 1 <= g[i], s[j] <= 231 - 1
1.2 笔记

贪心算法就是局部最优解来推出全局最优解。

那如何能保证最多的孩子能够吃饱呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其实思路也比较简单,先满足 用质量小的饼干胃口小的孩子,这样就能保证满足的孩子最多。

所以为了保证这个顺序,需要先将孩子的胃口和饼干的质量做一个排序,然后使用双指针去遍历这两个数组,直到饼干分发完或者孩子全部满足。

// 将两个数组排序
Arrays.sort(g);
Arrays.sort(s);
int i = 0; // 指向 g 的指针
int j = 0; // 指向 s 的指针
while(i < g.length && j < s.length) {
	// ... 
}

while 循环中,当饼干可以满足孩子的时候就同时移动指针,否则就只移动饼干数组的指针直到能找到满足孩子的饼干为止。

1.3 代码
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        int num = 0;
        // 将两个数组排序
        Arrays.sort(g);
        Arrays.sort(s);
        int i = 0; // 指向 g 的指针
        int j = 0; // 指向 s 的指针
        while(i < g.length && j < s.length) {
            if (s[j] >= g[i]) {
                num++;
                i++;
                j++;
            } else {
                j++;
            }
        }
        return num;
    }
}

02. 摆动序列(No. 376)

题目链接

代码随想录题解

2.1 题目

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

  • 例如, [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

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 1000
2.2 笔记

这道题目给我的感觉就是要考虑的条件太多了,先将大致的图画出来再来总结一共有哪些情况。

先来看最基础的情况,也就是没有平坡的时候:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

先来看判断摆动的依据,最终的结果通过 num 来存储;将本节点与上一个节点的差值设为 prediff 将下一个节点与本节点的差值定为 curdiff,判断摆动的条件就是 prediff < 0 && curdiff > 0prediff > 0 && curdiff < 0,取等的情况这里先按下不表。

因为要判断前后两部分的差值,所以 遍历必然是从中间开始,也就是 开头和结尾 必须做特殊的处理;但比如整个数组都是平的,比如这种情况:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这种情况下摆动序列的长度是 1,所以我们可以先选择 结尾的元素将其作为这个 1,所以结果 num 的初始值应该设为 1

说到这里很多朋友可能不理解

  • 先是上面这种情况,也就是完全的平坡,我们将 最后一个节点 作为选取的节点,最后返回 1

  • 然后来看第二种情况:
    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    当遍历到倒数第二个节点的时候因为形成了一个摆动,所以最终的结果需要 num++,得到的结果就是 2 也就是要求的最后一个结果。

处理完了结尾的元素,接下来就是 开头的元素 了,因为这里将结尾元素作为这个 1 ,结尾元素无需去遍历,但是开头元素是需要去遍历的,即遍历的范围是从 0nums.length - 1,这里的 prediff 的设置又成了问题,因为 nums[0] 前面是没有元素的。

所以这里采用经典的补节点的方式,在 nums[0] 这个节点前面再补充上一个节点

那补充上什么不会影响摆动序列的长度呢?

毫无疑问是补充上和 nums[0] 相同的节点,所以初始的 prediff 设置为 0,随后 prediff 随着 curdiff 做更新。

解决完开头和结尾就可以写出基本的代码了:

    int num = 1; // 结果
    int prediff = 0;
    for (int i = 0; i < nums.length - 1; i++) {
        int curdiff = nums[i + 1] - nums[i];
        if (prediff > 0 && curdiff < 0 || prediff < 0 && curdiff > 0) {
            num++;
        }
        prediff = curdiff;
    }
    return num;

提交后显然是不对的,因为还有 平坡 的情况需要讨论

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过上面的讲述,可以知道结果为 2 这个 2 是由最后一个节点和其前面一个节点构成的,那粉色的节点是否也能取代这个黄色的节点作为最终的节点呢?

答案是当然可以,这就是当遇到平坡的时候的应对策略的问题,可以选择删除粉色节点后面的所有节点,也可以选择删除黄色节点前面的所有节点,这两种策略是都可行的。

如果要删除黄色前面的所有节点或者粉色后面的所有节点,判断条件都是这样的:

if (prediff >= 0 && curdiff < 0 || prediff <= 0 && curdiff > 0) {
    num++;
}

当然黄色的很好理解,但是为什么粉色的也是这样的呢?这里仍然按下不表,留到下一种情况再做叙述。

到这里其实就可以写出较为完整的代码了:

        int num = 1; // 结果
        int prediff = 0;
        for (int i = 0; i < nums.length - 1; i++) {
            int curdiff = nums[i + 1] - nums[i];
            if (prediff >= 0 && curdiff < 0 || prediff <= 0 && curdiff > 0) {
                num++;
            }
            prediff = curdiff;
        }
        return num;

按照这样提交上去还是错误,因为还有最后一种情况没有考虑:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就是这种单调且含有平坡的情况,这种情况输出的是 1 但是通过上面的算法其实输出的是 2,是因为上面的算法取了粉色节点、黄色节点和最后一个节点,这是因为 prediff 随着 curdiff 做更新,导致后面的黄色节点也被取到了。

解决的方法就是 prediff 不随着 curdiff 做更新,而是当出现摆动的时候再做更新,即上面的 prediff 在黄色结点之前都取得粉色减去第一个节点值,这也就使得即使遍历到黄色位置的节点也不会出现 prediff 不对等 的情况。

所以这里我们既可以看作是删除掉粉色节点后面的节点,也可以看作是删除掉了黄色节点前面的节点

说到这里所有的情况就将讲完了,写出最终的代码。

2.3 代码
class Solution {
    public int wiggleMaxLength(int[] nums) {
        int num = 1; // 结果
        int prediff = 0;
        for (int i = 0; i < nums.length - 1; i++) {
            int curdiff = nums[i + 1] - nums[i];
            if (prediff >= 0 && curdiff < 0 || prediff <= 0 && curdiff > 0) {
                num++;
                prediff = curdiff;
            }
        }
        return num;
    }
}

03. 最大子数组和(No. 53)

题目链接

代码随想录题解

3.1 题目

给你一个整数数组 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

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
3.2 笔记

相比较贪心,本题的动态规划更容易理解,也是我第一个想到的方法。

动态规划最重要的是先来确定 dp[i] 代表着什么含义,思考一下,一个如果目前遍历到当前的一个节点,它只有三种选择:

  • 将本节点加到前面的节点上
  • 或者是从本节点开始遍历
  • 不取本节点

所以写出如下的代码

for (int i = 1; i < nums.length; i++) {
	dp[i] = Math.max(nums[i], dp[i-1] + nums[i]);
	dp[i] = Math.max(nums[i-1], dp[i]);
}

但是是错误的,因为在这种情况下取得的其实是整个数组的最大和,而不是最大连续子序列,因为在这里 dp[i] 的含义是在包含 nums[i] 的节点中任取之后得到的最大值。

而如果要连续的话,dp[i] 的值就是从本节点开始取值或者将本节点加到前面的连续节点上,这两个中的最大值。

所以写出来应该是:

for (int i = 1; i < nums.length; i++) {
	dp[i] = Math.max(nums[i], dp[i-1] + nums[i]);
}

然后还需要一个收集结果的节点,因为此时的 dp 数组的最后一个元素不一定是最大值。

for (int i = 1; i < nums.length; i++) {
    dp[i] = Math.max(nums[i], dp[i-1] + nums[i]);
    res = Math.max(res, dp[i]);
}

写出代码

3.3 代码
class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int res = nums[0];
        for (int i = 1; i < nums.length; i++) {
            dp[i] = Math.max(nums[i], dp[i-1] + nums[i]);
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

*Soo_Young*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值