代码随想录算法训练营第三十四天 | Java |134. 加油站 、135. 分发糖果、860.柠檬水找零、406.根据身高重建队列【贪心算法】

134. 加油站 

题目:在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

提示:

  • gas.length == n
  • cost.length == n
  • 1 <= n <= 105
  • 0 <= gas[i], cost[i] <= 104

思路:本题基础解题思路为暴力遍历,从第一个加油站开始,依次向后遍历判断当前油量总和sum=sum+gas[i]是否能大于当前油站消耗cost[i],如果小于则向后遍历直到遍历结束。因为题目设置为环形路线,此处使用取余操作模拟环路,当遍历一圈或找到符合要求的加油站时结束。 在力扣上暴力遍历超出时间限制,故此不推荐。

贪心算法局部最优解,根据每个油站的剩余油量rest[i]=gas[i]-cost[i]的累加结果来判断。在[0,i]的区间内,当curSum出现负数情况,即表示该区间内的节点不能满足供油需求,重新开始判断需从i+1号油站开始,当所有油站都计算完毕,根据totalSum结果判断当前开始油站是否满意要求。

注意:暴力遍历时为模拟环形数组使用取余操作和while循环配合。

Java实现

class Solution {
    // 暴力遍历,时间复杂度O(n^2),超出时间限制
    // public int canCompleteCircuit(int[] gas, int[] cost) {
    //     for(int i=0;i<gas.length;i++){
    //         int rest = gas[i]-cost[i];
    //         int start = (i+1)%gas.length;
    //         while(rest>0 && start!=i){
    //             rest += gas[start] - cost[start];
    //             start = (start+1)%gas.length;
    //         }
    //         if(rest>=0 && start==i){
    //             return i;
    //         }
    //     }
    //     return -1;
    // }

    // 局部最优,区间内[0,i]判断是否满足油量,不满足从i+1开始重新计算
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int curSum = 0;
        int totalSum = 0;
        int start = 0;
        for(int i=0; i<gas.length; i++){
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if(curSum<0){
                start = i+1;
                curSum = 0;
            }
        }
        if(totalSum<0){
            return -1;
        }
        return start;
    }
}

135. 分发糖果 

题目:n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。你需要按照以下要求,给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子评分更高的孩子会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

提示:

  • n == ratings.length
  • 1 <= n <= 2 * 104
  • 0 <= ratings[i] <= 2 * 104

思路: 借助卡尔老师的一句话,“贪心算法,两者兼顾容易顾此失彼!”。

本题分为确保“右边孩子比我大的比我多一”和“左边孩子比我大的比我多一”两部分。首先为第一个孩子分1个糖果,因为每个孩子至少有一个。为保证“左边孩子比我大的比我多一”,从左向右依次遍历,当遇到大的在当前孩子基础上+1,相同或者小的赋值为1。同理“右边孩子比我大的比我多一”为从右向左遍历。

注意:两个方向上分别遍历时,遍历区间要分清楚。从左向右为[1,ratings.length),从右向左为[0,ratings.length-2]。糖果赋值时可以使用 变量=(条件)?运算1:运算2 的操作。

Java实现

class Solution {
    public int candy(int[] ratings) {
        int counts = ratings.length;
        int[] count = new int[counts];
        count[0] = 1;
        for(int i=1; i<counts; i++){
            count[i] = (ratings[i]>ratings[i-1])?count[i-1]+1:1;
        }
        for(int i=counts-2; i>=0; i--){
            if(ratings[i]>ratings[i+1]){
                count[i] = (count[i]<count[i+1]+1)?count[i+1]+1:count[i];
            }
        }
        int sum = 0;
        for(int num : count){
            sum += num;
        }
        return sum;
    }
}

860. 柠檬水找零

题目:在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

提示:

  • 1 <= bills.length <= 105
  • bills[i] 不是 5 就是 10 或是 20

思路:本题初步思路为从前向后遍历,根据找零结果修改数组元素。当元素为五元时不做操作;元素为十元或二十元时,计算找零数值,在当前位置向前遍历减去数组值并保证找零值不为负。该思路弊端在于,当找零为十五元时,不能优先用十元,从而导致五元出现短缺。

再思考本题,数组中只有5,10,20三种情况。当5时,不需要找零操作,count5+1即可;当10时,需找零五元,即count5-1,count10+1;当20时,优先选择count10-1和count5-1的组合,若没有10,则选择count5-3的组合,因此当遍历中count5或count10出现负数,则证明找零失败。

注意:操作有限情况下优先考虑一一对应的解题思路。

Java实现

class Solution {
    // 55/61测试用例通过
    // public boolean lemonadeChange(int[] bills) {
    //     for(int i=0; i<bills.length; i++){
    //         if(bills[i]>5){
    //             int sign = i;
    //             int cash = bills[i] - 5;
    //             while(sign>0){
    //                 if(cash-bills[sign-1]>=0){
    //                     cash -= bills[sign-1];
    //                     bills[sign-1] = 0;
    //                     sign--;
    //                 }
    //                 else{
    //                     sign--;
    //                 }
    //             }
    //             if(cash>0){
    //                 return false;
    //             }
    //         }
    //     }
    //     return true;
    // }
    // 找零的情况只有三种,五元直接找零,十元用五元找零,二十元尽量用十元+五元找零
    public boolean lemonadeChange(int[] bills) {
        int five = 0;
        int ten = 0;
        for(int i=0; i<bills.length; i++){
            if(bills[i]==5){
                five++;
            }else if(bills[i]==10){
                five--;
                ten++;
            }else if(bills[i]==20){
                if(ten>0){
                    ten--;
                    five--;
                }
                else{
                    five -= 3;
                }
            }
            if(five<0 || ten<0){
                return false;
            }
        }
        return true;
        
    }
}

406. 根据身高重建队列

题目:假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)

提示:

  • 1 <= people.length <= 2000
  • 0 <= hi <= 106
  • 0 <= ki < people.length
  • 题目数据确保队列可以被重建

思路:解答本题与 135.分发糖果有相似的解题思路,贪心算法分别分两种条件逐一考虑。先根据身高从大到小排序,再根据k值逐一插入。但是很可惜答主本人一开始甚至没读懂题意,根据给出的示例结果也没理解,这题看了讲解才懂的。把讲解贴出来,朋友们自行学习一下 406.根据身高重建队列

注意:存在二维数组,Java中数据结构的选择

Java实现

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        // 身高排序
        Arrays.sort(people,(a,b)->{
            if(a[0]==b[0])
                return a[1]-b[1];
            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][]);

        
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值