Day29 贪心算法 part03

134. 加油站

本题有点难度,不太好想,推荐大家熟悉一下方法二

代码随想录

java 复制代码
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int sum = 0;
        int index = 0;
        int star = 0;
        int totalgas = 0;
        int totalcost = 0;

        for(int i = 0; i < gas.length; i++){
            totalgas += gas[i];
            totalcost += cost[i];
            int diff = gas[i] - cost[i];
            sum += diff;
            if(sum < 0){
                sum = 0;
                star = i + 1;
            }
        }

        if(totalgas < totalcost){
            return -1;
        }

        return  star;  
    }
}

总结

1.这个方法太巧妙了。核心思想就是计算油量-油耗,得到剩余油量。然后从头开始累加剩余油量,如果位置i出现sum为负数,说明前面随便哪个为起点都不能走到i的下一个。那么我们就以i+1为新的起点,把sum置为0。

2.这个方法就是把sum为负数的下一个坐标当作新的起点,也没有去统计把这个当作起点到底行不行。因为我们只要遵循上面的原则。把出现负数的下一个当作起点,那么就相当于在验证这个起点到底行不行,因为前面不行的起点,都被这个这个准则给淘汰了。

3.那sum为负数的下下个值就不能作为起点吗?答案是不行的,因为本题就只有1个答案,如果sum为负数的下一个值可以,说明后面的值都不行。如果sum为负数,更新为0后,加上下一个值还是负数,那就会再次更新起点。由于只有一个正确答案,那么这个答案一定是出现在负数的下一个。

4.然后如果出现总油量不够总的消耗量的话,那就返回-1。

5.总的来说,这道题坚持的原则就是出现sum为负数,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算sum。

135. 分发糖果

本题涉及到一个思想,就是想处理好一边再处理另一边,不要两边想着一起兼顾,后面还会有题目用到这个思路

代码随想录

java 复制代码
class Solution {
    
    public int candy(int[] ratings) {
        int[] result = new int[ratings.length];
        Arrays.fill(result, 1);

        //从前往后遍历,往右边看
        for(int i = 0; i < ratings.length - 1; i++){
            if(ratings[i+1] > ratings[i]){
                result[i+1] = result[i] + 1;
            }
        }

        //从后往前遍历,往左边看
        for(int i = ratings.length - 1; i > 0; i--){
            if(ratings[i-1] > ratings[i]){
                result[i-1] = Math.max(result[i] + 1, result[i-1]);
            }
        }

        return Arrays.stream(result).sum();
        
    }
}

总结

1.这道题需要先确定一边,再确定另一边。如果同时考虑两边,就容易顾此失彼。具体来说就是,我们先从前往后遍历,这时候就向右边看,如果右边孩子比当前孩子高,那么右边孩子就在当前孩子的基础上+1。这样遍历一般后,我们就确保了往右边看,只要评分更高,那么糖果肯定就越多。然后我们再从后往前遍历一般,这时候往左边看,如果左边的孩子比当前孩子高,那么就在当前孩子的基础上+1。然后最后取得值应该是既要满足从前往后遍历,也要满足从后往前遍历,取一个最大值就可以了。

2.这样本题我采用了两次贪心的策略:

  • 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。(往右边看,确保只要右边孩子评分高,就有更多的糖果)
  • 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。(往左边看,确保只要左边孩子评分高,就有更多的糖果)

经过前后两次遍历,取最大值之后,就可以从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。

3.这道题千万不能既往左边看,又往右边看,这样只会顾此失彼。我们分开来考虑,先往左边看,然后再往右边看。这样就不会出错。

4.注意for循环的边界。

860.柠檬水找零

本题看上好像挺难,其实很简单,大家先尝试自己做一做。

代码随想录

java 复制代码
class Solution {

    public boolean lemonadeChange(int[] bills) {
        int five = 0;
        int ten = 0;
        int twenty = 0;

        for(int i = 0; i < bills.length; i++){

            if(bills[i] == 5){
                five++;
            }

            if(bills[i] == 10){
                ten++;
                if(five > 0){
                    five--;
                }else{
                    return false;
                }           
            }

            if(bills[i] == 20){
               twenty++;
               if(ten > 0 && five > 0){
                    ten--;
                    five--;
               } else if(five >= 3){
                    five -= 3;
               } else{
                    return false;
               }
            }
        }

        return true;
        
    }
}

总结

1.这道题其实挺简单的,但一开始没有做出来主要是没有想明白该怎么表示收到5,或者找出5的数量呢?想的是把数组里面的元素删除的方法。但其实直接定义三个变量表示5元,10元,15元的数量就可以了。

2.这道题对billis数组遍历。无非就是三种情况,第一种是5,那么就直接收下,增加一张5元纸钞。第二种是10,那么就要消耗一张5元纸钞,增加一张10元纸钞。第三种是20,这时候就要用到贪心算法了,我们要先使用10+5的组合,因为5元更加灵活,如果前面一种不行,然后再判断 5x3的组合。

3.遇到感觉没有思路的题目,可以静下心来把能遇到的情况分析一下,只要分析到具体情况了,一下子就豁然开朗了。像这道题碰到5元和10元纸钞,策略是固定的。碰到20的,很自然想到要先把10元给花掉,因为5元花起来更加灵活。

406.根据身高重建队列

本题有点难度,和分发糖果类似,不要两头兼顾,处理好一边再处理另一边。

代码随想录

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];  //降序排列
        });

        List<int[]> que = new LinkedList<>();

        for(int[] row : people){
            que.add(row[1], row);
        }
        int[][] result = que.toArray(new int[people.length][]);
        //int[][] result = que.toArray(new int[0][]);


        
        return result;
      
    }
}

总结

1.这道题还是先考虑一个维度,然后再考虑另一个维度。我们可以先按照升高按降序排列,然后再对排序后的数组进行遍历,按照k值直接作为下标add到集合就行。

2.难点有三个。一个是一定是要先考虑一个维度,那到底是先考虑身高还是k值,可以都试一下。本题是先考虑身高,然后通过K值来调整。另一个是怎么对二维数组进行排序,这里记住Arrays.sort()默认是升序的。然后就是一维数组和二维数组都是引用数据类型,可以直接使用Arrays.sort(T[] t, Comparator<? super T> c),不需要转为Stream处理。

3.第三个就是 集合的使用。一开始没做出来就是不知道怎么动态的调整元素的顺序,这里我们可以先使用LinkedList,因为**LinkedList<int[]>** 是基于 双向链表 实现的,适合插入、删除的操作,ArrayList<int[]> 是基于 动态数组 实现的,适合查询的操作。List集合插入操作不会覆盖元素,而是将元素插入指定位置,之后的元素会向后移动。在下标为 0 插入元素时,所有原本在 0 及之后的元素都会依次向后移动,确保它们不丢失。但在Map 中,如果你插入的 key 已经存在,那么该 key 对应的值会被新值覆盖。Map 中的 key 是唯一的,不允许多个相同的 key 存在。如果插入相同的 key,则会更新(覆盖)其对应的 value

4.最后我们要把List集合转为数组。toArray():无参方法,将集合转换为 Object[] 数组。toArray(T[] array):带类型参数的方法,将集合转换为指定类型的数组,推荐使用这种方法。如果是一维数组:必须指定数组的长度,new int[5] 会创建一个长度为 5 的整数数组。如果是二维数组:可以只指定第一维的大小,例如 new int[5][],表示创建一个具有 5 行的二维数组,但列数可以动态分配。

  1. list.toArray(new String[0]) ; list.toArray(new int[0][ ]) ; 为什么传入 new String[0]?这是一个常见的技巧,因为它允许 toArray 根据集合的大小来创建合适大小的数组。传入一个零长度数组不会对性能产生影响,反而能确保返回的数组是正确的类型。
相关推荐
孑么15 分钟前
力扣 二叉树的中序遍历
java·算法·leetcode·职场和发展
码农多耕地呗16 分钟前
贪心—排序不等式——acwing
算法
Theodore_102223 分钟前
10 设计模式之装饰模式
java·开发语言·算法·设计模式·java-ee·装饰模式
colman wang39 分钟前
C语言刷题笔记3(7)
c语言·笔记·算法
laufing1 小时前
OD E卷 - 实现【流浪地球】
python·算法·逻辑模拟
pursuit_csdn1 小时前
力扣 48. 旋转图像
数据结构·算法·leetcode·矩阵·力扣
Reeeeeeeeeee-2 小时前
[MRCTF2020]Transform
c语言·开发语言·汇编·c++·算法
秋落风声2 小时前
【二维动态规划:交错字符串】
算法·leetcode·动态规划
Koishi_TvT2 小时前
蓝桥杯c++算法秒杀【6】之动态规划【上】(数字三角形、砝码称重(背包问题)、括号序列、组合数问题:::非常典型的必刷例题!!!)
c++·学习·算法·蓝桥杯·深度优先·动态规划·c
秋意钟3 小时前
Diff差异算法
算法