Day29--贪心--134. 加油站,135. 分发糖果,860. 柠檬水找零,406. 根据身高重建队列

发布于:2025-08-07 ⋅ 阅读:(16) ⋅ 点赞:(0)

Day29–贪心–134. 加油站,135. 分发糖果,860. 柠檬水找零,406. 根据身高重建队列

134. 加油站

方法:循环数组,模拟

思路:

循环数组模拟闭环的效果。

最重要的剪枝:当油不够的时候,直接从断油的这个点i开始循环。

自己写的有点累赘。可以看下一个优化版本。

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int n = gas.length;
        // 这个循环,把每次出发的情况都考虑到
        for (int k = 0; k < n; k++) {
            // 这个循环,指的是从这个点出发转一圈行不行。是起到转圈作用。
            boolean flag = true;
            int oil = 0;
            int meet = 0;
            int i = k;
            for (; i < n * 2 && meet != 2; i++) {
                // 用meet参数来判断是否闭环(走回原点)
                if (meet == k) {
                    meet++;
                }
                oil += gas[i % n] - cost[i % n];
                if (oil < 0) {
                    flag = false;
                    break;
                }
            }
            // 只有两种情况会退出上面的循环,一是meet==2,也就是闭环了。另一种是断油了。
            if (flag) {
                return k;
            }
            // 最重要的是这一步:当油不够的时候,直接从断油的这个点i开始循环
            // 不做这一步剪枝的话会超时,时间是O(n^2),数据量10^5*10^5
            if (!flag) {
                k = i;
            }
        }
        return -1;
    }
}

方法:贪心

思路:

利用curRest和totalRest,一次循环搞定。关键要理解,为什么当curRest<0之后,下一个起点是i+1,这和数学/逻辑证明有关。

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {

        int startIndex = 0;
        // 当前剩余油量
        int curRest = 0;
        // 总剩余油量
        int totalRest = 0;

        for (int i = 0; i < gas.length; i++) {
            // 加油减油
            curRest += gas[i] - cost[i];
            totalRest += gas[i] - cost[i];

            // 当前累加rest[i]和 curRest一旦小于0,起始位置更新为i+1,并且curRest从0开始
            if (curRest < 0) {
                startIndex = i + 1;
                curRest = 0;
            }
        }
        // 总油量剩余小于0的话,说明怎么走都不可能跑一圈了
        if (totalRest < 0) {
            return -1;
        }
        return startIndex;
    }
}

135. 分发糖果

方法:贪心

思路:

  • 从左向右遍历。只要i个孩子比i-1那个孩子厉害,就比他多一颗糖。
  • 从右向左遍历。如果i个孩子,大于i+1个孩子了。要判断,是要比i+1多一颗糖呢?还是领原来的打败左边孩子的糖呢?取二者之间较大值
class Solution {
    public int candy(int[] ratings) {
        int n = ratings.length;
        if (n == 0) {
            return 0;
        }
        int[] candys = new int[ratings.length];
        // 每个人至少得到1颗糖
        Arrays.fill(candys, 1);
        // 从左向右遍历。只要i个孩子比i-1那个孩子厉害,就比他多一颗糖
        for (int i = 1; i < n; i++) {
            if (ratings[i - 1] < ratings[i]) {
                candys[i] = candys[i - 1] + 1;
            }
        }
        // 从右向左遍历。
        // 如果i个孩子,大于i+1个孩子了。要判断,是要比i+1多一颗糖呢?还是领原来的打败左边孩子的糖呢?
        // 取二者之间较大值
        for (int i = n - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1]) {
                candys[i] = Math.max(candys[i + 1] + 1, candys[i]);
            }
        }

        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += candys[i];
        }
        return sum;
    }
}

860. 柠檬水找零

方法:贪心

思路:

当收20时,先找10块和5块,再考虑三张5块。(搞笑的事,20块没用,不用记账)

// 当收20时,先找10块和5块,再考虑三张5块
class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0;
        int ten = 0;
        for (int i = 0; i < bills.length; i++) {
            switch (bills[i]) {
                case 5: {
                    five++;
                    break;
                }
                case 10: {
                    // 找一张5块,收一张10块
                    if (five > 0) {
                        five--;
                        ten++;
                    } else {
                        return false;
                    }
                    break;
                }
                case 20: {
                    // 先找一张10和5,最大程度保留5,因为当付10块的时候也要找钱5块
                    if (ten > 0 && five > 0) {
                        ten--;
                        five--;

                        // 如果没有,就要消耗3张5块
                    } else if (five >= 3) {
                        five -= 3;
                    } else {
                        return false;
                    }
                }
            }
        }
        return true;
    }
}

406. 根据身高重建队列

方法:贪心

思路:

  • 排序规则:
    • 身高从大到小排列(降序)
    • 若身高相同,则按照k值从小到大排列(升序)
  • 遍历排序后的数组,按照k值插入到对应位置

记录:特别地,需要掌握Arrays.sort()的高级排序操作,和LinkedList的toArray()方法。

class Solution {
    /**
     * 根据身高和位置信息重建队列
     * 
     * @param people 二维数组,每个元素包含两个值:[身高, 前面身高大于等于当前的人数]
     * @return 重建后的队列数组
     */
    public int[][] reconstructQueue(int[][] people) {
        // 对数组进行排序
        // 排序规则:
        // 1. 身高从大到小排列(降序)
        // 2. 若身高相同,则按照k值从小到大排列(升序)
        Arrays.sort(people, (a, b) -> {
            // 身高相同的情况,按k值升序排列
            if (a[0] == b[0]) {
                return a[1] - b[1];
            }
            // 身高不同的情况,按身高降序排列
            return b[0] - a[0];
        });

        // 创建链表用于插入操作
        LinkedList<int[]> que = new LinkedList<>();

        // 遍历排序后的数组,按照k值插入到对应位置
        for (int[] p : people) {
            // LinkedList的add(index, element)方法:
            // 将元素插入到指定索引位置,原索引及之后的元素后移
            que.add(p[1], p);
        }

        // 将链表转换为数组并返回
        return que.toArray(new int[people.length][]);
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到