LeetCodeCampsDay29贪心part03
LeetCodeCampsDay29贪心part03
有些题目有多个维度时,可以去去除一个维度(比如406)
而有些题目需要同时考虑左右相邻元素时,可以先从左向右、再从右向左遍历
134. 加油站
https://leetcode.cn/problems/gas-station/
在一条环路上有 n
个加油站,其中第 i
个加油站有汽油 gas[i]
升。
你有一辆油箱容量无限的的汽车,从第 i
个加油站开往第 i+1
个加油站需要消耗汽油 cost[i]
升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas
和 cost
,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1
。如果存在解,则 保证 它是 唯一 的。
示例 1:
1 | 输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2] |
示例 2:
1 | 输入: gas = [2,3,4], cost = [3,4,3] |
提示:
n == gas.length == cost.length
1 <= n <= 105
0 <= gas[i], cost[i] <= 104
- 输入保证答案唯一。
Gas | 2 | 5 | 2 | 3 | 5 |
---|---|---|---|---|---|
Cost | 1 | 2 | 8 | 2 | 4 |
restGas | 1 | 3 | -6 | 1 | 1 |
Index | 0 | 1 | 2 | 3 | 4 |
开始令下标从0开始,就累加计算当前restGas量,如果当前累加量小于0,比如到达第三号时,此时剩余-2,则说明无论是从1号还是2号出发,肯定都会无法走完全程的,所以将下标从3开始继续尝试。
贪心思路
本题目可以不用管所谓的循环的条件,只关心剩余gas量
先求出每站跑一遍能剩下的gas量(rest = gas - cost),再直接以第一站开始遍历,将每站rest量添加到remain,如果到了第i站,remain出现负数,说明从i站之前所有站的出发,都无法完成目标;需要从第i+1站继续尝试,如果i+1站走到了终点,则说明i+1站可以作为起点;
这里有点儿难理解,为什么i+1站并没有走完所谓的全程(至少没有循环地走完全程),而只是从i+1走到L,就可以说明它可以作为起点;
因为题目里保证,如果有答案,只唯一,所以这里贪心的思路是,删除所有不可能的点,剩下可能的一定是答案
总和是关键:如果整个环的总 rest 和 >= 0,那么一定有一个唯一起点
先考虑整个数组的 total rest(即所有 rest[i] 的和)。
如果 total rest < 0,意味着总油量不足以绕环一周,无论从哪里出发,都不可能成功。
如果 total rest >= 0,问题保证了只有一个起点能成功。
这是因为环路的性质:油量是固定的,总供给 >= 总消耗,所以只要找到那个“平衡点”,就能走完全程。
算法利用这一点:在遍历过程中,我们不是在真正模拟开车,而是在检查累积油量是否可持续。如果从某个起点开始,油量 remain 就负了,意味着这个起点不行,但我们知道其他起点中有一个是行的
当我们重置起点到i+1并继续累积remain,实际上是在从i+1开始检查剩余路径是否可持续。同时,之前的失败已经帮我们"过滤"了无效起点
最终,遍历完整个数组后,如果总remain>=0,说明从i+1开始的路径是可行的,因为总rest和>=0,且我们已经排除了前面的无效点
用一个例子说明
假设我们有一个环形数组:
gas = [1, 2, 3, 4, 5]
cost = [3, 4, 5, 1, 2]
那么 rest = [1-3, 2-4, 3-5, 4-1, 5-2] = [-2, -2, -2, 3, 3]
现在,按算法遍历:
开始从索引 0,remain = 0
i=0: remain += -2 = -2 (负数!) → 起点无效,移到 i+1=1,重置 remain=0
i=1: remain += -2 = -2 (负数!) → 起点无效,移到 i+2=2,重置 remain=0
i=2: remain += -2 = -2 (负数!) → 起点无效,移到 i+3=3,重置 remain=0
i=3: remain += 3 = 3
i=4: remain += 3 = 6 (结束遍历)
总 remain = 6 >= 0,所以起点是 3(i+1 从上一步的 i=2)。
为什么 i=3 可以直接作为起点?因为:
从 0、1、2 开始都失败了,证明它们不可行。 总 rest 和 = -2 + -2 + -2 + 3 + 3 = 0 (>= 0),所以一定有一个起点(这里是 3)。 我们不需要从 i=3 单独再走一遍全程,因为算法的最后累积 already 确认了它能覆盖整个环。
另外,为什么找到了第一个可以走完剩下路的点i就是起点?或者说,第一个让remain一直保持大于0的点就是起点
这好理解,哪怕有第二个点能让remain在i+1到L的路程一直大于0,那第一个点肯定更能满足在i到L的路程一直大于0(因为第一个点的rest要大于0,而且第二个点已经保持了后续的reamin大于0),那第一个点就必然是“唯一的答案”
贪心代码
1 | class Solution: |
135. 分发糖果
https://leetcode.cn/problems/candy/
n
个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1
个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
1 | 输入:ratings = [1,0,2] |
示例 2:
1 | 输入:ratings = [1,2,2] |
提示:
n == ratings.length
1 <= n <= 2 * 104
0 <= ratings[i] <= 2 * 104
贪心思路
本题需要左、右都比较,但如果想一次遍历就比较左右两边是比较难的;所以可以遍历两次,一次从左向右比较,判断右边比左边高;一次从右向左比较,判断左边比右边高的情况
第一次遍历时,if ratings[i] > ratings[i - 1],则res[i] = res[i - 1] + 1
第二次遍历时,if ratings[i] > ratings[i + 1],需要注意,不能直接res[i] = res[i + 1] + 1,因为题目要求当前孩子需要比左、右孩子相对比,所以这里需要将res[i + 1] + 1(左边比右边高的情况)与res[i](之前得到的,右边比左边高的情况进行比较),取最大值res[i] = max(res[i], res[i + 1] + 1)
最后返回sumOfNums即可
贪心代码
- 时间复杂度O(N)
- 空间复杂度O(1)
1 | class Solution: |
860. 柠檬水找零
https://leetcode.cn/problems/lemonade-change/
在柠檬水摊上,每一杯柠檬水的售价为 5
美元。顾客排队购买你的产品,(按账单 bills
支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5
美元、10
美元或 20
美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5
美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills
,其中 bills[i]
是第 i
位顾客付的账。如果你能给每位顾客正确找零,返回 true
,否则返回 false
。
示例 1:
1 | 输入:bills = [5,5,5,10,20] |
示例 2:
1 | 输入:bills = [5,5,10,10,20] |
提示:
1 <= bills.length <= 105
bills[i]
不是5
就是10
或是20
贪心思路
本题只需要讨论找钱的几种情况,记数手里5元、10元的个数,并算欠用户多少钱,是否能还
- 收到5元,直接收入;不找
- 收入10元,检测是否有5元,有则找;没有则False
- 收到20元,优先检测是否有一张10元一张5元,如果没有再检测是否有三张5元,有则找;没有则False
贪心代码
- 时间复杂度O(N)
- 空间复杂度O(1)
1 | class Solution: |
406. 根据身高重建队列
https://leetcode.cn/problems/queue-reconstruction-by-height/
假设有打乱顺序的一群人站成一个队列,数组 people
表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki]
表示第 i
个人的身高为 hi
,前面 正好 有 ki
个身高大于或等于 hi
的人。
请你重新构造并返回输入数组 people
所表示的队列。返回的队列应该格式化为数组 queue
,其中 queue[j] = [hj, kj]
是队列中第 j
个人的属性(queue[0]
是排在队列前面的人)。
示例 1:
1 | 输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]] |
示例 2:
1 | 输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]] |
提示:
1 <= people.length <= 2000
0 <= hi <= 106
0 <= ki < people.length
- 题目数据确保队列可以被重建
贪心思路
本题目有高度和顺序两个维度,同时处理起来很麻烦,所以先进行降维度;只处理高度
1、优先按h排序,从高到低,身高相同再按k从小到大;
比如
[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]
按身高先排序后会得到
[7,0],[7,1],[6,1],[5,0],[5,2],[4,4]
2、再按k进行排序,这里只要按k将元素插入在k位置即可
贪心代码
- 时间复杂度:O(nlog n + n^2)
- 空间复杂度:O(n)
1 | class Solution: |