代码随想录day34 贪心算法

文章介绍了使用贪心算法解决数组取反以最大化和、环路加油站问题以及分发糖果问题。在数组问题中,通过先对数组排序,然后优先处理负数和最小值,达到最优解。在加油站问题中,寻找起点并累加油量差,确定能完成环路的起点。分发糖果问题则通过两次遍历,确保相邻孩子得分递增,计算最小糖果总数。
摘要由CSDN通过智能技术生成

代码随想录day34 贪心算法

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

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)

启发

1,虽然是简单题,但一开始并没有想到如何求解。看了题解以后在最后一步还是思考了很久。
2,首先想到的一定是先将数组中的负数取反,如果全部的负数变为正数以后k的次数没有用完那就再将数组中最小的那个数不断来回取反,这里是需要特别注意的地方,因为题目中说了一个元素可以重复取反。(我就是卡在这里了,想不通为啥最后题解是判断k的奇偶)最后k为奇数就将最小的元素取反,为偶数就不用取反(其实就是取偶数次反以后正负性不变)。
3,那么如何来保证按照绝对值由大到小的顺序排列(这样才能先将绝对值大的负数取反),采用stream流很方便。
4,本题是如何体现贪心的呢:首先如果将最大的负数取反,就会得到最大的和。其次,如果所有的负数变为了正数次数k还没有用完,就将最小的数不断取反。这两个步骤都是体现了局部最优全局最优。

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        //先将数组按照绝对值大小从大到小排序,用流排序
        nums = IntStream.of(nums)
                .boxed() //将int流转换为Integer流
                .sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1)) //按绝对值从大到小排列
                .mapToInt(Integer::intValue) //双冒号表示方法的引用
                .toArray();
        
        for(int i = 0; i < nums.length; i++) {
            if(nums[i] < 0 && k > 0){
                nums[i] = 0 - nums[i];
                k--;
            }
        }
        if(k % 2 == 1) nums[nums.length - 1] = 0 - nums[nums.length - 1];
        return Arrays.stream(nums).sum();
    }
}

题134 加油站

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

说明:
如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。
示例 1: 输入:
gas = [1,2,3,4,5]
cost = [3,4,5,1,2]

启发

1,其实本题还是挺难理解的。需要搞清楚几点:
(1)起点的位置一定是gas大于cost的位置。
(2)从头开始要将gas和cost的差值累加记录,即使是负值,那么当遇到正值时来补齐,遍历一遍之后如果能够补齐就代表可以跑完一圈,否则不行。
(3)那么如何记录能够跑完一圈的起始位置,方法2中通过记录每一次gas[i] - cost[i] 第一次>= 0的位置再加上判断从i开始是否往后区间中都可以保证油箱中剩余油量>=0 来记录。

class Solution {
    //暴力两层循环解法超时了。
    // public int canCompleteCircuit(int[] gas, int[] cost) {
    //     int gasSum = Arrays.stream(gas).sum();
    //     int costSum = Arrays.stream(cost).sum();
    //     if(costSum > gasSum) return -1;
    //     for(int i = 0; i < gas.length; i++) {
    //         int rest = gas[i] - cost[i];//记录剩余油量
    //         int index = (i + 1) % cost.length;
    //         while(rest > 0 && index != i) {//模拟以i为起点转一圈
    //             rest += gas[index] - cost[index];
    //             index = (index + 1) % cost.length;
    //         }
    //         if(rest >= 0 && index == i) return i;
    //     }
    //     return -1;
    // }

    //方法1:从全局最优进行思考,分几种情况
    // public int canCompleteCircuit(int[] gas, int[] cost) {
    //     int curSum = 0; //记录油箱中的油总量
    //     int minRest = 0;// 记录油箱中的剩余最小值,如果这个值>0,则代表从0开始油箱一直没有空过。
    //     for(int i = 0; i < gas.length; i++) {
    //         int rest = gas[i] - cost[i];
    //         curSum += rest;
    //         minRest = Math.min(minRest, curSum);
    //     }

    //     if(curSum < 0) return -1; //遍历一遍之后油量为负,肯定不能跑完一圈
    //     if(minRest >= 0) return 0; //代表油箱一直没空过,那么从第0站开始可以一直跑一圈
    //     //否则minRest中记录的是最大亏空油量(一个负数),从后往前看,慢慢将亏空补起来,补到哪里就从哪里作为出发点
    //     for(int i = gas.length - 1; i >= 0; i--) {
    //         int rest = gas[i] - cost[i];
    //         minRest += rest;
    //         if(minRest >= 0) return i;
    //     }
    //     return -1;
    // }

    //方法2: 从局部最优思考,贪心算法
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int curSum = 0; //记录某区间油箱的油量
        int totalSum = 0; //记录从第0站开始油箱的油量
        int index = 0;
        for(int i = 0; i < gas.length; i++) {
            int rest = gas[i] - cost[i];
            curSum += rest;
            totalSum += rest;
            //从0开始,一旦油箱里的油量小于0,则从0到当前i肯定不是合法区间,index肯定要从i+1开始
            //且要从头开始累加curSum。
            //这里应该要考虑到环状,如果i + 1 为最后一个元素,则index要从0开始。
            //index其实是记录了一个区间的开头,这个区间中油箱的油量一直为正数
            if(curSum < 0) {
                index = (i + 1) % gas.length;
                curSum = 0; 
            }
        }
        if(totalSum >= 0) return index;
        return -1;
    }
}

题135 分发糖果

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?

示例 1:
输入: [1,0,2]
输出: 5
解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。

启发

1,分两步实现最优,一是先和左边比较,一是和右边比较,分开进行。
2,新建一个数组保存每个孩子分到的糖果数量,先给第一个孩子1颗糖,然后从第二个孩子开始遍历,一次与左边的孩子进行比较,如果比左边的孩子分数高就比左边的孩子多一个糖果,此时的数组保证了左边正确。
3,在从右往左遍历,从倒数第二个孩子开始,比较其与右边孩子的分数,高则取右边孩子糖果数量加1和原本拥有的糖果数量的最大值(需要注意的地方),否则糖果数量不变。
4,为什么第二次要从右往左遍历,因为这样才能在更新过的糖果数量上进行再次更新,比如倒数第2个孩子比倒数第1个孩子分数高,那么倒数第2个孩子的糖果数量更新一次,倒数第3个孩子分数又比倒数第2个孩子分数高,那么倒数第3个孩子的糖果数量需要在刚才更新过的倒数第2个孩子的糖果数量基础上再次更新。如果从左往右就没办法利用已经更新过的数据。

class Solution {
    public int candy(int[] ratings) {
        int len = ratings.length;
        int[] candyNum = new int[len]; //记录每个孩子得到的糖的个数
        candyNum[0] = 1; //先给第一个孩子一颗糖(每个孩子最少会有一颗),后面从第二个孩子开始遍历
        //1. 先从前向后遍历,来比较当前孩子和前面的孩子。
        for(int i = 1; i < len; i++) {
            if(ratings[i] - ratings[i - 1] > 0) {
                candyNum[i] = candyNum[i - 1] + 1;// 如果当前孩子比之前的孩子分高,多一个糖
            } else {
                candyNum[i] = 1;//否则只给一个糖
            } 
        }
        //2. 再从后向前遍历,来比较当前的孩子和后一个孩子。
        for(int i = len - 2; i >=0; i--) {
            if(ratings[i] - ratings[i + 1] > 0) {
                candyNum[i] = Math.max(candyNum[i + 1] + 1, candyNum[i]);//注意此处取两个值中的最大值
            } 
        }
        return Arrays.stream(candyNum).sum();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值