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;
}
}