代码随想录刷题day29丨134. 加油站,135. 分发糖果,860.柠檬水找零 , 406.根据身高重建队列
1.题目
1.1加油站
-
-
文档讲解:https://programmercarl.com/0134.%E5%8A%A0%E6%B2%B9%E7%AB%99.html
-
解题思路:贪心
-
首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。
-
每个加油站的剩余量rest[i]为gas[i] - cost[i]。
-
i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。
-
图示
-
-
代码:
//时间复杂度:O(n) //空间复杂度:O(1) class Solution { public int canCompleteCircuit(int[] gas, int[] cost) { int curSum = 0; int totalSum = 0; int start = 0; for(int i = 0;i < gas.length;i++){ totalSum += gas[i] - cost[i]; curSum += gas[i] - cost[i]; if(curSum < 0){ start = i + 1; curSum = 0; } } if(totalSum < 0){ return -1; } return start; } }
-
总结:
- 局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。
- 全局最优:找到可以跑一圈的起始位置。
1.2分发糖果
-
-
文档讲解:https://programmercarl.com/0135.%E5%88%86%E5%8F%91%E7%B3%96%E6%9E%9C.html
-
解题思路:贪心
- 这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
- 先确定右边评分大于左边的情况(也就是从前向后遍历)
- 局部最优:只要右边评分比左边大,右边的孩子就多一个糖果
- 全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
- 再确定左孩子大于右孩子的情况(从后向前遍历)
-
代码:
class Solution { public int candy(int[] ratings) { //数组纪录糖果数 int[] candys = new int[ratings.length]; //给每个孩子先发一个糖果 for(int i = 0;i < ratings.length;i++){ candys[i] = 1; } //右孩子比左孩子分高 for(int i = 1;i < ratings.length;i++){ if(ratings[i] > ratings[i - 1]){ candys[i] = candys[i - 1] + 1; } } //左孩子比右孩子分高 for(int i = ratings.length - 2;i >= 0;i--){ if(ratings[i] > ratings[i + 1]){ candys[i] = Math.max(candys[i + 1] + 1,candys[i]); } } int result = 0; for(int i = 0;i < ratings.length;i++){ result += candys[i]; } return result; } }
-
总结:
- 确定左孩子大于右孩子的情况一定要从后向前遍历!
- 本题采用了两次贪心的策略:
- 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
- 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
1.3柠檬水找零
-
题目链接:860. 柠檬水找零 - 力扣(LeetCode)
-
文档讲解:https://programmercarl.com/0860.%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.html
-
解题思路:贪心
- 三种情况:
- 情况一:账单是5,直接收下。
- 情况二:账单是10,消耗一个5,增加一个10
- 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
- 三种情况:
-
代码:
class Solution { public boolean lemonadeChange(int[] bills) { int five = 0, ten = 0; for (int bill : bills) { if (bill == 5) { five++; // 收到一张 $5 } else if (bill == 10) { if (five == 0) { return false; // 没有 $5 找零 } five--; // 找一张 $5 ten++; // 收到一张 $10 } else { // 处理 $20 if (ten > 0 && five > 0) { ten--; // 优先找一张 $10 和一张 $5 five--; } else if (five >= 3) { five -= 3; // 没有 $10 时,用三张 $5 找零 } else { return false; // 无法找零 } } } return true; // 能给所有顾客找零 } }
-
总结:
- 局部最优:遇到账单20,优先消耗美元10,完成本次找零。
- 全局最优:完成全部账单的找零。
1.4根据身高重建队列
-
题目链接:406. 根据身高重建队列 - 力扣(LeetCode)
-
视频讲解:贪心算法,不要两边一起贪,会顾此失彼 | LeetCode:406.根据身高重建队列_哔哩哔哩_bilibili
-
文档讲解:https://programmercarl.com/0406.%E6%A0%B9%E6%8D%AE%E8%BA%AB%E9%AB%98%E9%87%8D%E5%BB%BA%E9%98%9F%E5%88%97.html
-
解题思路:贪心
-
对于本题相信大家困惑的点是先确定k还是先确定h呢,也就是究竟先按h排序呢,还是先按照k排序呢?
如果按照k来从小到大排序,排完之后,会发现k的排列并不符合条件,身高也不符合条件,两个维度哪一个都没确定下来。
那么按照身高h来排序呢,身高一定是从大到小排(身高相同的话则k小的站前面),让高个子在前面。
此时我们可以确定一个维度了,就是身高,前面的节点一定都比本节点高!
-
排完h后,只需要按照k为下标重新插入队列就可以了,为什么呢?
-
以图中{5,2} 为例:
-
按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。
-
-
-
代码:
class Solution { public int[][] reconstructQueue(int[][] people) { //身高从大到小排列 Arrays.sort(people,(a,b) -> { if(a[0] == b[0]){ return a[1] - b[1]; }else{ return b[0] - a[0]; } }); LinkedList<int[]> que = new LinkedList<>(); for(int[] p : people){ que.add(p[1],p); } return que.toArray(new int[people.length][]); } }
-
代码解释
Arrays.sort(people, (a, b) -> { ... });
:对people
数组进行排序。people
是一个二维数组,每个子数组包含两项[hi, ki]
,其中hi
是身高,ki
是前面有多少人比他高或者身高相同的。if (a[0] == b[0]) { return a[1] - b[1]; }
:如果两个元素的身高相同,则按ki
的升序排列。这里a[1] - b[1]
实现了升序排序。return b[0] - a[0];
:如果身高不同,则按身高的降序排列。这里b[0] - a[0]
实现了降序排序。LinkedList<int[]> que = new LinkedList<>();
:创建一个空的LinkedList
,用来存储重建后的队列。for (int[] p : people) { ... }
:遍历排序后的people
数组中的每一个子数组p
。que.add(p[1], p);
:将p
插入到链表que
的索引位置p[1]
。这里p[1]
表示在队列中应该放置的位置(即ki
值),p
是需要插入的数组[hi, ki]
。return que.toArray(new int[people.length][]);
:将链表que
转换为二维数组并返回。new int[people.length][]
提供了目标数组的大小以确保正确转换。 -
流程
- 排序:先按身高降序排列,同身高时按
ki
升序排列。 - 重建队列:根据排序后的顺序将每个人插入到链表的指定位置。
- 转换并返回:将链表转换为二维数组形式返回。
- 排序:先按身高降序排列,同身高时按
-
-
总结:
- 局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性
- 全局最优:最后都做完插入操作,整个队列满足题目队列属性