2022.04.13 力扣1005,134,860

本文通过四个具体的编程题目,展示了贪心算法在解决实际问题中的应用。题目涉及数组取反最大化、加油站问题、分发糖果和柠檬水找零。在每个问题中,贪心策略被用来优化解决方案,例如:优先处理绝对值大的负数、确保每次取反后油量足够、确保每个孩子都能得到至少一颗糖果,并在找零时尽量用最少的硬币。
摘要由CSDN通过智能技术生成

学习:贪心算法

follow:代码随想录


1005 K次取反后最大化的数组和

题目描述:
给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。
重复这个过程恰好 k 次。可以多次选择同一个下标 i

以这种方式修改数组后,返回数组 可能的最大和 。

解析:
简单的贪心思想:
有负数的情况:将绝对值大的负数取反
有正数的情况:将绝对值小的正数取反
⚠️:每次取反后都需要重新排序,效率低

改进的贪心思想:
有负数且取反次数没达到阈值的情况:将绝对值大的负数取反
已经将所有负数都去过一次反但还是没有达到取反次数,此时应该把小的正数取反,此时又有负数了,下一次需要取反时,应该把该负数取反
所以整体的思路是:现将负数取反,如果达到阈值,可以直接累加到末尾输出,如果没有达到阈值,则看差奇数次还是偶数次,如果为奇数次,则在总和的基础上减去二倍的最小正数,否则直接输出和

 public int largestSumAfterKNegations(int[] nums, int k) {
        Arrays.sort(nums);
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            if(nums[i] < 0 && k > 0){
                nums[i] = - nums[i];
                sum += nums[i];
                k--;
            }else{
                sum += nums[i];
            }
        }
        Arrays.sort(nums);
        return sum - (k % 2 == 0 ? 0 : nums[0] * 2);
    }
134 加油站问题

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

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

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

解析:
贪心思想:
如果环路上总的gas大于cost则则证明,从某个点出发,可以绕环路一周。
假设rest为从某个点i起始,到达点j时汽油的剩余量,如果剩余量小于0,则证明,从i点无法到达j点,则[i,j]中的任意一点,都不会是起点。则从j+1开始,直到遍历到最后一加油站。

public static int canCompleteCircuit(int[] gas, int[] cost) {
        //贪心思想:从0开始累计rest,一旦rest<0,说明从起点到无法该位置,所以起点从i+1开始
        //只需要遍历到length为止,不需要计算到起点,因为总rest一定大于0,才能到达终点,否则无法到达
        int rest = 0;
        int begin = 0;
        int total = 0;
        for(int i = 0; i < gas.length; i++){
            total += gas[i] - cost[i];
            rest += gas[i] - cost[i];
            if(rest< 0){
                rest = 0;
                begin = i + 1;
            }
        }
        return total >= 0 ? begin : -1;

    }
135 分发糖果

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

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

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子评分更高的孩子会获得更多的糖果。(但是如果两个孩子评分相等,则不必一样的糖果)

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

解析:
暴力算法:

  • 首先找到最小的评分,最小的评分一定获得最少的糖果
  • 给最少评分的孩子分发糖果后,根据题意为其两侧的孩子分糖果,此时分的糖果应该是最低要求,以后可能会因为其他孩子的限制而修改糖果数量
  • 再为两侧的孩子分糖果
	int[] candys;
    public int candy(int[] ratings) {
        if(ratings.length == 1) return 1;
        candys = new int[ratings.length];
        sub_candy(ratings,0, ratings.length);
        int sum = 0;
        for(int i : candys){
            sum += i;
        }
        return sum;
    }

    public void sub_candy(int[] ratings, int begin, int end){
        if(end - begin <= 1) return;
        //寻找最小值
        int min = ratings[begin];
        int min_index = begin;
        for(int i = begin + 1; i < end; i++){
            if(ratings[i] < min){
                min = ratings[i];
                min_index = i;
            }
        }
        //找到最小值后,两侧二分
        //填充最小值
        candys[min_index] = Math.max(candys[min_index], 1);
        //填充两侧
        if(min_index > begin){
            if(ratings[min_index - 1] > ratings[min_index]){
                candys[min_index - 1] = Math.max(candys[min_index] + 1, candys[min_index - 1]);
            }else{
                candys[min_index - 1] = Math.max(candys[min_index - 1], 1);
            }

        }
        if(min_index < end - 1){
            if(ratings[min_index + 1] > ratings[min_index]){
                candys[min_index + 1] = Math.max(candys[min_index] + 1, candys[min_index + 1]);
            }else{
                candys[min_index + 1] = Math.max(candys[min_index + 1], 1);
            }
        }
        //二分
        sub_candy(ratings, begin, min_index);
        sub_candy(ratings, min_index + 1, end);
    }

贪心算法:

  • 第一次从左到右遍历,如果右侧大于左侧,那么右侧就等于左侧加一,不可以先考虑左侧大于右侧的情况,因为此时没有考虑到右侧的右侧;此时会忽略左侧大于右侧的情况
  • 第二次从右到左遍历,考虑、左侧大于右侧的情况
 public int candy(int[] ratings) {
        int[] candy = new int[ratings.length];
        Arrays.fill(candy, 1);
        //从左到右
        for(int i = 1; i < ratings.length; i++){
            if(ratings[i] > ratings[i - 1]) candy[i] = candy[i - 1] + 1;
        }
        //从右到左
        for(int i = ratings.length - 2; i >= 0; i--){
            if(ratings[i] > ratings[i + 1]) candy[i] = Math.max(candy[i], candy[i + 1] + 1);
        }
        int sum = 0;
        for(int i : candy){
            sum += i;
        }
        return sum;
    }
860 柠檬水找零

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

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

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

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

解析:
简单来说:

  • 如果顾客给5元,则直接收下
  • 如果给10元,找一个5元,如果不剩下5元,则返回false
  • 如果给20,要么找一个10块,一个5块,要么找三个五块,如果不满足,则返回false
public boolean lemonadeChange(int[] bills) {
        int[] charge = new int[2];//分别存储5、10
        for(int i = 0; i < bills.length; i++){
            if(bills[i] == 5) charge[0]++;
            else if(bills[i] == 10){
                if(charge[0] > 0){
                    charge[0]--;
                    charge[1]++;
                }else{
                    return false;
                }
            }else{
                if(charge[1] > 0){
                    charge[1]--;
                    bills[i] -= 10;
                }
                while(bills[i] > 5 && charge[0] > 0){
                    bills[i] -= 5;
                    charge[0]--;
                }
                if(bills[i] > 5) return false;
            }
        }
        return true;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值