代码随想录刷题day29丨134. 加油站,135. 分发糖果,860.柠檬水找零 , 406.根据身高重建队列

代码随想录刷题day29丨134. 加油站,135. 分发糖果,860.柠檬水找零 , 406.根据身高重建队列

1.题目

1.1加油站

  • 题目链接:134. 加油站 - 力扣(LeetCode)

    在这里插入图片描述

  • 视频讲解:贪心算法,得这么加油才能跑完全程!LeetCode :134.加油站_哔哩哔哩_bilibili

  • 文档讲解: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分发糖果

  • 题目链接:135. 分发糖果 - 力扣(LeetCode)

    在这里插入图片描述

  • 视频讲解:贪心算法,两者兼顾很容易顾此失彼!LeetCode:135.分发糖果_哔哩哔哩_bilibili

  • 文档讲解: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)

    在这里插入图片描述

  • 视频讲解:贪心算法,看上去复杂,其实逻辑都是固定的!LeetCode:860.柠檬水找零_哔哩哔哩_bilibili

  • 文档讲解: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满足队列属性
    • 全局最优:最后都做完插入操作,整个队列满足题目队列属性
  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值