Day27 力扣贪心 : 1005.K次取反后最大化的数组和| 134. 加油站 |135. 分发糖果

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

本题简单一些,估计大家不用想着贪心 ,用自己直觉也会有思路。
https://programmercarl.com/1005.K%E6%AC%A1%E5%8F%96%E5%8F%8D%E5%90%8E%E6%9C%80%E5%A4%A7%E5%8C%96%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.html

第一印象:

按直觉想,就是选尽量小的数字变成负数,越小的负数反过来越大,越小的正数反过来影响越小,那就先排序呗。

做完了不对,因为可以多次选择同一个下标。

那就每次取反之后再排序.

ok 做对了

看完题解的思路:

题解强调要用贪心的思路去思考一下

  • 如果有负数,局部绝对值大的负数变成正数,整体最大
  • 全是正数,局部最小的正数变成负数,整体最大

所以我觉得就是,局部最小的数字取负,整体最大

但是题解的java答案好复杂,我这个很简单啊

实现中的困难:

没有审题出,可以反复变动一个元素的下标,比如 -1 0 2 3 ,k=3,应该是最大6,如果排序一次,从index=0的地方开始k个都取反,就是 1 0 -2 3,答案就是2了,就不是最大

所以应该排序之后,每次取反,都再排序一次,确保变动的是最小的那个。

感悟 :

有点不理解题解,但是确实要用贪心的思路去想,不然贪心简单题简单做,贪心难题根本不会。

代码:

class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        //排序
        Arrays.sort(nums);

        //最小的k个取反
        while (k-- != 0) {
            nums[0] = -nums[0];
            //确保每次取反的都是最小的
            Arrays.sort(nums);
        }
            
        //求和
        int sum = 0;
        for (int i = 0; i <nums.length; i++) {
            sum += nums[i];
        }
        return sum;
    }
}

134. 加油站

本题有点难度,不太好想,推荐大家熟悉一下方法二
https://programmercarl.com/0134.%E5%8A%A0%E6%B2%B9%E7%AB%99.html

第一印象:

感觉就是两个for循环遍历,

诶不对,怎么模拟出这个环呢,i < length 就结束了啊

直接看看题解吧。

看完题解的思路:

哎卧槽太神奇了,有一种前面那和最大的子序列的感觉。负数对整体的影响是负面的 那个味道。

求出每站净油量的数组 rest[]。比如 1 3 -6 1 1.

遍历这个数组,累计curGas模拟油箱的油。如果curGas < 0了,就说明这个起点不行,就从 i + 1 开始,start 记录这个起点。遍历一遍数组,就可以找到起点。

那么我就产生两个问题:

那有没有可能 [0,i] 区间 选某一个作为起点,累加到 i这里 curGas是不会小于零呢?

如果有这样的一个位置k,[0, k -1] [k, i]两个区间,如果从k开始到i可以让curGas > 0, 而不从i + 1开始。那么[0, k -1] 这段就一定是 负的,那么按这个逻辑,就已经该从 k 作为起始位置开始了,就没有这个问题了。

好啊,那找到下一个起点 i + 1之后,是不是要模拟循环一圈回来呢? 为啥答案只是 for 循环一遍数组就结束了?

比如 1 3 -1000 4 5. curGas 到-1000 < 0 了,就从4 开始,接下来的curGas = 4,又遍历到5,cruGas = 9. 那么就返回了从4 这里开始啊???

但是显然这个情况跑不完一圈,不模拟从4 跑一圈,怎么能说起始位置是 4 就可以呢。

这就是totalGas的作用了,如果totalGas < 0 那么一定是不可能的。而题干说,存在的话一定就一个,要不就不存在。所以totalGas > 0 的话就是一定存在,我们去找它在哪就可以了。

实现中的困难:

这道题逻辑还是挺神奇的。代码实现没难度

感悟:

这道题的贪心思路是:

局部最优: 当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。

全局最优: 找到可以跑一圈的起始位置。

代码:

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int totalGas = 0;
        int curGas = 0;
        int start = 0;
        int[] rest = new int[gas.length];

        //初始化 rest数组,求和total
        for (int i = 0; i < rest.length; i++) {
            rest[i] = gas[i] - cost[i];
            totalGas += rest[i];
        }
        //如果不存在直接返回-1
        if (totalGas < 0) return -1;
        //存在的话去找这个位置
        for (int i = 0; i < rest.length; i++) {
            //模拟油箱从这个起点开始
            curGas += rest[i];
            //这个起点不行啊
            if (curGas < 0) {
                //那就从它下一个开始试试,更新到start
                start = i + 1;
                //油箱模拟起点的时候是 0 
                curGas = 0;
            }
        }

        return start;
    }
}

135. 分发糖果

本题涉及到一个思想,就是想处理好一边再处理另一边,不要两边想着一起兼顾,后面还会有题目用到这个思路
https://programmercarl.com/0135.%E5%88%86%E5%8F%91%E7%B3%96%E6%9E%9C.html

第一印象:

处理好一边,再处理另一边?

一开始我想排序,然后一层一层的给,觉得很简单。

但其实这道题是不能排序的,因为顺序影响给糖果的数量,比如1 0 2,可以2 1 2的给。排序了的话就是 1 2 3的给了。

不行 没思路 看题解了

看完题解的思路:

哎卧槽真神奇。我一开始也觉得 如果 rating[0] < rating[1] 就可以从1开始了,也就是从第一个小孩1颗糖开始发。

但是rating[0] > rating[1] ,rating[1] > rating[2] …… 这样的话,我就没法给第一个小孩发糖了,我也看不到头啊。然后我就混乱不会了。

其实这种问题,就是一边,另一边吧。 每个小孩要和左右的小孩都比较。那就先比较一遍左边,再倒着遍历比较一遍右边。最后取最大值就可以了

为什么从后向前? 因为“我比右边大,我要多拿” 的情况下,是基于右边的,我顿悟了。

为什么取最大值呢? 因为要满足两种情况呀,其实自己写个例子,左边一遍,右边一遍,收获结果的时候自然取得就是最大值了。

实现中的困难

实现中没困难,以 “我比左边大,我要比他多拿一个” “我比右边大,我要比他多拿一个” 两个思路去写就可以了

感悟:

代码随想录里总结:

如果在考虑局部的时候想两边兼顾,就会顾此失彼。

那么本题我采用了两次贪心的策略:

一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。

代码:

class Solution {
    public int candy(int[] ratings) {
        int[] candy = new int[ratings.length];
        //初始化
        for (int i = 0; i < candy.length; i++) {
            candy[i] = 1;
        }
        //我和左边比, 第一个元素不能和左边比
        for (int i = 1; i < candy.length; i++) {
            //左边比我小,我多拿糖
            if (ratings[i] > ratings[i - 1]) {
                candy[i] = candy[i - 1] + 1;
            } 
        }
        //我和右边比,最后一个元素不能和右边比
        for (int i = candy.length - 2; i >= 0; i--) {
            //右边比我小,我多拿糖
            if (ratings[i] > ratings[i + 1]){
                //取这种情况和第一种情况更大的更新到 i 上
                candy[i] = Math.max(candy[i + 1] + 1, candy[i]);
            }
        }
        int sum = 0;
        for (int i = 0; i < candy.length; i++) {
            sum += candy[i];
        }

        return sum;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值