6.3 贪心算法
前言
贪心算法一般出现在笔试题中,面试出现的概率非常低。贪心算法理解并不难,他区分于我们常用DFS/BFS/DP,他并不会枚举所有情况,而是只选择其中“当前最优”的情况。因此,贪心算法的思想并不难,甚至代码也并不难实现,但是难点在于找到贪心的策略和证明贪心策略有可行性。
贪心的正确性严格意义上来说是需要数学证明的,但这超出了数据结构的范畴,特别是笔试的时候,几乎不会有充足的时间让我们去证明贪心的正确性。因此,笔试的时候更多是观察数据的范围,如果数据量比较大,我们就可以往贪心的方向去尝试,注意这里的措辞:尝试。笔试的时候贪心算法的正确性往往是试出来的。
对于贪心,如果准备时间不是非常充裕的同学,可以不用花费太多时间,知道大概思考方向即可,因为即使花费再多时间训练贪心,效果依然是不明显的。
给定一个区间的集合 intervals
,其中 intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 示例 1: 输入: intervals = 输出: 1 解释: 移除 后,剩下的区间没有重叠。
示例 2: 输入: intervals = 输出: 2 解释: 你需要移除两个 来使剩下的区间没有重叠。
示例 3: 输入: intervals = 输出: 0 解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
提示: 1 <= intervals.length <= 10^5 intervals.length == 2 - 5 * 10^4 <= starti < endi <= 5 * 10^4
|
题目解析
区间问题的贪心策略常见的有以下几种情况:
- 每次选择开始时间最早的
- 每次选择结束时间最早的
- 每次选择总耗时最少的
- 每次选择与最少重叠的。
此题选择策略2进行贪心。
因为结束时间越早,可选的区间就越多。这是最直观的一个解释,如果要严格地证明的话,方法如下:
- 与其他方案相比,此算法的选择方案在选取了相同数量的更早开始的区间时,其最终结束时间不会比其他方案晚。
- 因此,不存在选择更多区间的方案。
代码展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution {
public int eraseOverlapIntervals(int[][] intervals) { Arrays.sort(intervals, (a, b) -> a[1] - b[1]); int cnt = 1, pre = intervals[0][1]; for (int i = 1; i < intervals.length; i++) { if (intervals[i][0] >= pre) { cnt++; pre = intervals[i][1]; } } return intervals.length - cnt; } }
|
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i
,都有一个胃口值 g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j
,都有一个尺寸 s[j]
。如果 s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 示例 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 * 10^4 0 <= s.length <= 3 * 10^4 1 <= g[i], s[j] <= 2^31 - 1
|
题目解析
首先将孩子数组和饼干数组排序。首先将最小的饼干尝试匹配胃口最小的孩子,如果匹配成功,那就直接考虑下一个;否则,则说明这个饼干无法做出贡献。
代码展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Solution {
public int findContentChildren(int[] g, int[] s) { Arrays.sort(g); Arrays.sort(s); int cnt = 0; int i = 0, j = 0; for (; i < s.length && j < g.length; i++) { if (s[i] >= g[j]) { cnt++; j++; } } return cnt; } }
|
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 示例 1: 输入:prices = [7,1,5,3,6,4] 输出:7 解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。 总利润为 4 + 3 = 7 。
示例 2: 输入:prices = [1,2,3,4,5] 输出:4 解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 总利润为 4 。
示例 3: 输入:prices = [7,6,4,3,1] 输出:0 解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。
提示: 1 <= prices.length <= 3 * 10^4 0 <= prices[i] <= 10^4
|
题目解析
由于此题不限制买卖次数,因此此题的贪心策略如下:
如果今天买明天卖可以赚钱,那么我们就执行这个操作。
代码展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution {
public int maxProfit(int[] prices) { int profit = 0; for (int i = 1; i < prices.length; i++) { if (prices[i] > prices[i - 1]) { profit += prices[i] - prices[i - 1]; } } return profit; } }
|