代码随想录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();
}
}