贪心类题目专题训练 -- LeetCode上10道与贪心相关的题

这个专题中的题目是我跟随代码随想录的刷题计划,在LeetCode上做的与贪心相关的题目,用于加深对贪心的理解!

下面的内容将会有每一道题目的题意、在代码随想录中对应的参考文章、我的思路以及我所写的Java代码,希望对你有帮助!


1 - LeetCode 455 分发饼干

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/assign-cookies

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

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


参考文章

思路:

这道题是最简单的贪心题。先将小孩的胃口和饼干的大小都排序,然后将大的饼干优先考虑安排给胃口大的小孩,这样子就能通过贪心得到最优解。

本题Java代码:

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int rn = s.length - 1;
        int ans = 0;
        for (int i = g.length - 1; i >= 0; i--) {
            if (rn < 0) break;
            if (s[rn] >= g[i]) {
                ans++;
                rn--;
            }
        }
        return ans;
    }
}

 

 

2 - LeetCode 376 摆动序列

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/wiggle-subsequence

题意:

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

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


参考文章

思路:

这道题没看参考文章之前实在没能想到还能这么做!

思路是直接把这道题化为求序列中峰值的个数,具体过程可以参考下图(图片转自代码随想录):在这里插入图片描述
使用curDiff记录当前两个数字的差值,preDiff记录之前的差值,如果当前的差值出现跟之前的差值相反,说明单调性发生变化,出现峰值。

这里要注意preDiff不是前两个数的差值,而是只有每次判断出现新峰值时才更新preDiff,因为preDiff代表的是序列之前最近出现的单调性

接下来就是处理前后两个节点的问题。使用的方法是先初始化preDiff为0,然后在判断条件的地方做下简单修改即可!(这里如果不理解的话可以看参考文章)

注意ans的初始值是为1,因为序列个数最小为1,答案最小也就是为1。

本题Java代码:

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

 
 

3 - LeetCode 53 最大子数组合

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-subarray

题意:

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


参考文章

思路:

利用贪心的想法,遍历数组的过程中,不断对当前和进行判断。

  • 如果当前和大于目前最大和,则更新最大和
  • 如果当前和小于0,直接丢弃前面全部数字,将当前和重设为0

如何理解第二点?我们要的答案是一个连续子数组的和,出现一个负数的当前和,说明这个子数组中前面的数字的总和只会拖累后面的数字来获得更大的和,那么我们使用贪心策略,直接将前面的数字都舍弃掉即可!

过程可以参考下图(图片转自代码随想录):

注意答案要初始化为最小数而不能是0,因为数组中的数字可能都为负数。

本题Java代码:

class Solution {
    public int maxSubArray(int[] nums) {
        int ans = Integer.MIN_VALUE, rnSum = 0;
        for (int i = 0; i < nums.length; i++) {
            rnSum += nums[i];
            ans = Math.max(ans, rnSum);
            if (rnSum < 0) rnSum = 0;
        }
        return ans;
    }
}

 
 

4 - LeetCode 122 买卖股票的最佳时机

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii

题意:

给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。

在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。
返回 你能获得的 最大 利润 。

示例 1:

输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

输入: prices = [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: prices = [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

1 <= prices.length <= 3 * 104
0 <= prices[i] <= 104


参考文章

思路:

这道题虽然在LeetCode上是中等的难度,但是它使用贪心来做真的好简单。

直接算出每相邻两天的差值,也即是每一天的利润,如果当天的利润可以大于0,那就拿下这个利润!

参考下图(图片转自代码随想录):
在这里插入图片描述
简单明了,代码也没几行。

本题Java代码:

class Solution {
    public int maxProfit(int[] prices) {
        int ans = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] - prices[i - 1] > 0) ans += prices[i] - prices[i - 1];
        }
        return ans;
    }
}

 
 

5 - LeetCode 55 跳跃游戏

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game

题意:

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。

示例 1:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2:

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

提示:

1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105


参考文章

思路:

仍然是一道非常简单的贪心题,即便它的难度被标为中等。

不要从每次应该去跳跃几步的角度去思考,而是要算出当前能跳跃到的最大范围即可。

使用longest变量记录当前可以跳跃到达的最远下标,在for循环中 i 不会大于当前可以跳跃到的最远下标。如果当前位置加上当前位置可以跳跃到达的距离大于目前可以到达的最远距离,则更新longest。

结果如果longest大于或等于数组的最大下标,意味着可以跳跃到最后一个下标,返回true。

代码上的注意点:

  • longest是从0开始计数,代表的是能抵达的最远的下标,因此在结果判断时候记得是判断longest是否大于或等于nums.length - 1
  • 在for循环中 i 除了不能大于longest之外,还不能大于数组的大小,不然可能出现下标越界的错误

本题Java代码:

class Solution {
    public boolean canJump(int[] nums) {
        int longest = 0;
        for (int i = 0; i <= longest && i < nums.length; i++) {
            longest = Math.max(i + nums[i], longest);
        }
        if (longest >= nums.length - 1) return true;
        return false;
    }
}

 
 

6 - LeetCode 45 跳跃游戏 II

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game-ii

题意:

给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。

示例 1:

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:

输入: nums = [2,3,0,1,4]
输出: 2

提示:

1 <= nums.length <= 104
0 <= nums[i] <= 1000


参考文章

思路:

这道题是上面那道题的升级版,但其实思路差不多,都是要计算得出可以到达的最远距离。

不过这道题需要记录两个最远距离,一个是当前步数下所能到达的最大下标,一个是在当前步数下再多加一步所能到达的最大下标。

使用rnLongest代表当前步数下所能到达的最大下标,使用nextLongest代表当前步数下再多跳一步所能到达的最大下标。

在for循环中,当 i 等于rnLongest时,说明其实已经到达了当前步数下所能到达的最大距离,当前步数无法满足抵达终点,那么我们就将答案+1,然后将rnLongest更新为nextLongest。

当rnLongest >= nums.length - 1,意味着当前步数下可以抵达终点,直接返回答案。

代码上的注意:
注意当数组大小为1时,由于起始位置就在下标为1的位置,所以无需跳跃,答案为0

本题Java代码:

class Solution {
    public int jump(int[] nums) {
        if (nums.length == 1) return 0;
        int rnLongest = 0;//当前步数所能抵达的最远距离
        int nextLongest = 0;//当前步数下多跳一步后所能抵达的最远距离
        int ans = 0;//当前步数
        for (int i = 0; i < nums.length; i++) {
            nextLongest = Math.max(nextLongest, i + nums[i]);
            if (i == rnLongest) {
                ans++;
                rnLongest = nextLongest;
                if (rnLongest >= nums.length - 1) return ans;
            }
        }
        return ans;
    }
}

 
 

7 - LeetCode 1005 K次取反后最大化的数组和

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximize-sum-of-array-after-k-negations

题意:

给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。
重复这个过程恰好 k 次。可以多次选择同一个下标 i 。

以这种方式修改数组后,返回数组 可能的最大和 。

示例 1:

输入:nums = [4,2,3], k = 1
输出:5
解释:选择下标 1 ,nums 变为 [4,-2,3] 。

示例 2:

输入:nums = [3,-1,0,2], k = 3
输出:6
解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。

示例 3:

输入:nums = [2,-3,-1,5,-4], k = 2
输出:13
解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。

提示:

1 <= nums.length <= 104
-100 <= nums[i] <= 100
1 <= k <= 104


参考文章

思路:

这是一道非常简单的贪心题目,基本上一看到题意就能知道如何贪心地解决。

先将数组排序,这时候绝对值最大的负数排在最前面。

在第一个for循环中,从第一个元素开始,也即绝对值最大的负数,将它们设为相反数,同时消耗反转次数,k减1。直到遇到了正数或者抵达数组最后一个元素或者消耗完反转次数。

这时候再将数组排序,获得最小的数字,如果此时反转次数有剩余,则不断将这个数字进行反转即可。如果此时k为奇数,则该数设为相反数,否则不变。

最后所有数字相加并返回结果即可。

本题Java代码:

import java.util.Arrays;

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        Arrays.sort(nums);
        for (int i = 0; i < nums.length && k > 0 && nums[i] < 0; i++) {
            nums[i] = -nums[i];
            k--;
        }
        Arrays.sort(nums);
        if (k % 2 == 1) nums[0] = -nums[0];
        int ans = 0;
        for (int i : nums) ans += i;
        return ans;
    }
}

 
 

8 - LeetCode 134 加油站

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/gas-station

题意:

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。

示例 2:

输入: gas = [2,3,4], cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

提示:

gas.length == n
cost.length == n
1 <= n <= 105
0 <= gas[i], cost[i] <= 104


参考文章

思路:

这道题我们使用变量rn来代表此刻汽车中剩余的油。gas[i] - cost[i]代表经过 i 加油站时汽油的增加量(有可能是负数),将所有gas[i] - cost[i]相加,也即遍历了所有加油站后,如果此刻汽车中剩余的油rn为负数,说明是没法完成走一圈的,此时返回答案-1。

如果rn不为负数,说明可以实现走完一圈,那么必定存在从一个点出发可以走完这一圈且答案唯一。那么我们就取rn值最小时的加油站的下一个加油站即可!因为rn值最小,意味着抵达这个加油站时消耗是最大的,既然可以实现走完这一圈,那么我们从这个加油站的下一个加油站出发即可!

代码上注意的点:
在返回答案时,因为我们是要返回minIndex的下一个坐标,minIndex有可能是数组中的最后一个下标,这时候+1会越界,所以我们要加入一个判断,如果minIndex是数组最后一个下标,就返回0

本题Java代码:

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int rn = 0, minIndex = 0, min = Integer.MAX_VALUE;
        for (int i = 0; i < gas.length; i++) {
            rn += gas[i] - cost[i];
            if (rn < min) {
                minIndex = i;
                min = rn;
            }
        }
        if (rn >= 0) return minIndex + 1 >= gas.length ? 0 : minIndex + 1;
        return -1;
    }
}

 
 

9 - LeetCode 135 分发糖果

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/candy

题意:

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

示例 1:

输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。

示例 2:

输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。

提示:

n == ratings.length
1 <= n <= 2 * 104
0 <= ratings[i] <= 2 * 104


参考文章

思路:

这是一道困难难度的题目,但也是可以用贪心的算法来解决。

candy数组代表需要额外分配给小朋友的糖果。

我们先从左向右扫描,如果小朋友分数比前一个小朋友分数高,就让他的糖果数为前一个小朋友的糖果数+1。

接下来我们再从右向左扫描,如果小朋友的分数比后一个小朋友分数高,就让他的糖果数为后一个小朋友的糖果数+1,但是这时候还要再跟之前从左向右扫描所得的糖果数比较并取最大值(取最大值的原因是这个小朋友之前从左向右扫描时得到的顺位就是这样,只有取得较大值才能满足所有小朋友两边的方向都能比邻近的小朋友高)。

最后把糖果总和相加就得到答案。

过程可以参考以下图(图片转自代码随想录):
在这里插入图片描述
在这里插入图片描述

本题Java代码:

class Solution {
    public int candy(int[] ratings) {
        int[] candy = new int[ratings.length];
        int ans = ratings.length;
        for (int i = 1; i < ratings.length; i++) {
            if (ratings[i] > ratings[i - 1]) candy[i] = candy[i - 1] + 1;
        }
        for (int i = ratings.length - 1; i >= 0; i--) {
            if (i < ratings.length - 1 && ratings[i] > ratings[i + 1]) candy[i] = Math.max(candy[i], candy[i + 1] + 1);
            ans += candy[i];
        }
        return ans;
    }
}

 
 

10 - LeetCode 860 柠檬水找零

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lemonade-change

题意:

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

示例 1:

输入:bills = [5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。

示例 2:

输入:bills = [5,5,10,10,20]
输出:false
解释:
前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
由于不是每位顾客都得到了正确的找零,所以答案是 false。

示例 3:

输入:bills = [5,5,10]
输出:true

示例 4:

输入:bills = [10,10]
输出:false

提示:

1 <= bills.length <= 105
bills[i] 不是 5 就是 10 或是 20


参考文章

思路:

非常简单的题目。贪心的思想在于,当收到的钱是20的时候,优先使用一张10元和一张5元去找零,实在不行再用三张5元去找零。

本题Java代码:

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0, ten = 0;
        for (int i : bills) {
            if (i == 5) five++;
            else if (i == 10) {
                five--;
                ten++;
            } else if (ten > 0) {// i == 20
                ten--;
                five--;
            } else {// i == 20
                five -= 3;
            }
            if (five < 0 || ten < 0) return false;
        }
        return true;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值