贪心算法:加油站、分发糖果、柠檬水找零、根据身高重建队列、周总结

11. 加油站

例题134:
在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

在这里插入图片描述
局部最优:每次选择gas-cost>=0的加油站出发,整体最优:使得油量可以满足加油站
可以换一个思路,首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。

每个加油站的剩余量rest[i]为gas[i] - cost[i]。

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。

如图:
在这里插入图片描述

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int start=0;
        int curSum=0;
        int totalSum=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;
    }
}

11. 分发糖果(困难)

例题135:
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

在这里插入图片描述
这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼

先确定右边评分大于左边的情况(也就是从前向后遍历)

此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果

局部最优可以推出全局最优。

如果ratings[i] > ratings[i - 1] 那么[i]的糖 一定要比[i - 1]的糖多一个,所以贪心:candyVec[i] = candyVec[i - 1] + 1。

再确定左边大于右边的情况(也就是从后往前遍历)

遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?

因为 rating[5]与rating[4]的比较 要利用上 rating[5]与rating[6]的比较结果,所以 要从后向前遍历。

如果从前向后遍历,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。

class Solution {
    public int candy(int[] ratings) {
int sum=0;
        int[] candies=new int[ratings.length];
        candies[0]=1;
        for(int i=1;i<ratings.length;i++){
              candies[i]=(ratings[i]>ratings[i-1])?candies[i-1]+1:1;
              
        }
        for(int i=ratings.length-2;i>=0;i--){
            if(ratings[i]>ratings[i+1]){
                candies[i]=Math.max(candies[i+1]+1,candies[i]);//这里不能盲目的让[i+1]的糖果+1,例如2,3,2的糖果经过从前往后遍历的糖果是1,2,1,这时从后往前遍历,糖果数2是取2和1+1的较大值
            }
        }
        for(int i=0;i<ratings.length;i++){
            sum+=candies[i];
        }
        return sum;
    }
}

12. 柠檬水找零

例题860:
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

在这里插入图片描述

class Solution {
    public boolean lemonadeChange(int[] bills) {
int[] nums=new int[]{0,0,0};
        boolean flag=true;
        for(int i=0;i<bills.length;i++){
            if(bills[i]==5){
                nums[0]++;
            }else if(bills[i]==10){
                nums[1]++;
                nums[0]--;
            }else{
                nums[2]++;
                if(nums[1]!=0){
                    nums[1]--;
                    nums[0]--;
                }
                else{
                    nums[0]-=3;
                }
            }
            if(nums[0]<0 || nums[1]<0){
                flag=false;
                break;
    }
}
return flag;
}
}

14. 根据身高重建队列

例题406:
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
在这里插入图片描述

思路

题目有h和k两个维度,这种遇到两个维度的时候,一定要先确定一个维度,再确定另一个维度。
如果两个维度一起考虑一定会顾此失彼。

那么,应该是先排身高,还是人头?
如果先排人头,会发现k的排序并不符号条件,身高也不符合条件,两个维度都没确定下来。

而如果先排身高,让高个子在前面。
那么就可以确定身高的维度了,就是前面的节点都比本节点高。

那么只需要按照k为下标重新插入队列就可以了,为什么呢?

以图中{5,2} 为例:
在这里插入图片描述
按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。

所以在按照身高从大到小排序后:

局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性
全局最优:最后都做完插入操作,整个队列满足题目队列属性

Arrays.sort(people,(a,b)->{
            if(a[0]==b[0]) return a[1]-b[1];//a-b是升序排列,如果身高相等,则按照人头升序排列
            return b[0]-a[0];//b-a是降序,按照身高降序排列
        });
        LinkedList<int[]> que=new LinkedList<>();
        for(int[] p:people){
            que.add(p[1],p);//LinkedList.add(index,value),会将value插入到指定的index里
        }
        return que.toArray(new int[people.length][]); 
        }
        ```

>1. 怎么对Java中的二维数组排序?
>2. 怎么将数组转换为队列?
>3. 怎么根据二维数组的第二个元素的顺序将数组元素插入队列?
>4. 怎么将int[]队列又变为int[][]?

# 15. 贪心周总结
**加油站**:给出每一个加油站的汽油和到下一个加油站的消耗,问能否开满一圈?能的话输出起始位置,不能的话输出-1。
如果gas[i]-cost[i]<0,那么[0,i]的加油站都不能作为起始点,需要向后移一位。
所以需要两个sum记录从起始点的汽油和总汽油。
一旦当前汽油<0,就更新为0,并从下一位开始。最后判断总汽油<0就不能跑一圈。


**分发糖果:**相邻小孩,得分高的孩子糖果要多一个。
**这是第一次碰到两个维度的问题**,必须先满足一个维度,再满足另一个维度。
从前往后:满足右孩子大于左孩子。
从后往前:满足左孩子大于右孩子。

**柠檬水找零**:维护5,10,20的个数,一旦小于0就不能找零。
20的找零方式有两种:5+10和5+5+5。

**根据身高重建队列:**根据身高和前面高的人的数组重新排列。
两个维度:先满足身高降序,再根据人头插入。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值