贪心算法 part03
- 1005.K次取反后最大化的数组和
- 解题思路
- 134. 加油站
- 解题思路
- 注意点
- 135. 分发糖果
- 解题思路
1005.K次取反后最大化的数组和
本题简单一些,估计大家不用想着贪心 ,用自己直觉也会有思路。
题目链接: 1005.K次取反后最大化的数组和
文章/视频链接: 1005.K次取反后最大化的数组和
解题思路
贪心1
局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。
局部最优可以推出全局最优。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。
贪心2
那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。
本题的解题步骤为
:
第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
第二步:从前向后遍历,遇到负数将其变为正数,同时K–
第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
第四步:求和
// 贪心 leecode找的代码
// 相比于卡哥的代码 额外开辟了新空间list
class Solution {public int largestSumAfterKNegations(int[] nums, int K) {// 将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小List<Integer> list = new ArrayList<>();for(int num : nums){list.add(num);}list.sort((a, b) -> Math.abs(a) - Math.abs(b)); // 绝对值从小到大进行排序int ans = 0;for(int i = list.size() - 1; i >= 0; i--){if(K > 0 && list.get(i) < 0){ans += -list.get(i);K--;}else{ans += list.get(i);}}if(K % 2 != 0){ans -= 2 * Math.abs(list.get(0));}return ans;}
}
// 卡哥代码
class Solution {public int largestSumAfterKNegations(int[] nums, int K) {// 将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小nums = IntStream.of(nums).boxed().sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1)).mapToInt(Integer::intValue).toArray();int len = nums.length; for (int i = 0; i < len; i++) {//从前向后遍历,遇到负数将其变为正数,同时K--if (nums[i] < 0 && K > 0) {nums[i] = -nums[i];K--;}}// 如果K还大于0,那么反复转变数值最小的元素,将K用完if (K % 2 == 1) nums[len - 1] = -nums[len - 1];return Arrays.stream(nums).sum();}
}
134. 加油站
本题有点难度,不太好想,推荐大家熟悉一下方法二
题目链接: 134. 加油站
文章/视频讲解: 134. 加油站
解题思路
首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。
每个加油站的剩余量rest[i]为gas[i] - cost[i]。
i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。
局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。
全局最优:找到可以跑一圈的起始位置。
注意点
curSum是用来找可能的起始下标的,而不能用作最终判断能否可行的条件
最终还是得用totalSum判断
// 贪心
class Solution {public int canCompleteCircuit(int[] gas, int[] cost) {int curSum = 0; //记录目前油箱剩余量int totalSum = 0; // 记录总的油箱剩余量int index = 0; // 记录可能的可行出发位置for(int i = 0; i < gas.length; i++){curSum += gas[i] - cost[i];totalSum += gas[i] - cost[i];if(curSum < 0){curSum = 0;index = (i + 1) % gas.length; // 更新可能出发位置} } if(totalSum < 0){ // 遍历结束后总的油箱剩余量小于0return -1;}return index;}
}
135. 分发糖果
本题涉及到一个思想,就是想处理好一边再处理另一边,不要两边想着一起兼顾,后面还会有题目用到这个思路
题目链接: 135. 分发糖果
文章/视频讲解: 135. 分发糖果
解题思路
这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
先确定右边评分大于左边的情况(也就是从前向后遍历)
此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
局部最优可以推出全局最优。
那么本题我采用了两次贪心的策略:
- 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
- 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。
// 贪心/**分两个阶段1、起点下标1 从左往右,只要 右边 比 左边 大,右边的糖果=左边 + 12、起点下标 ratings.length - 2 从右往左, 只要左边 比 右边 大,此时 左边的糖果应该 取本身的糖果数(符合比它左边大) 和 右边糖果数 + 1 二者的最大值,这样才符合 它比它左边的大,也比它右边大*/
class Solution {public int candy(int[] ratings) {int len = ratings.length;int[] candyVec = new int[len];candyVec[0] = 1;// 从左往右遍历for(int i = 1; i < len; i++){if(ratings[i] > ratings[i-1]){candyVec[i] = candyVec[i-1] + 1;}else{candyVec[i] = 1;}}// 从右往左遍历for(int i = len-2; i >= 0; i--){if(ratings[i] > ratings[i+1]){candyVec[i] = Math.max(candyVec[i], candyVec[i+1] + 1);}}int ans = 0;for(int num : candyVec){ans += num;}return ans;}
}