代码随想录算法训练营第33天 || 1005.K次取反后最大化的数组和 || 134. 加油站 || 135. 分发糖果
1005.K次取反后最大化的数组和
题目介绍:
给你一个整数数组 nums
和一个整数 k
,按以下方法修改该数组:
- 选择某个下标
i
并将nums[i]
替换为-nums[i]
。
重复这个过程恰好 k
次。可以多次选择同一个下标 i
。
以这种方式修改数组后,返回数组 可能的最大和 。
示例 1:
输入:nums = [4,2,3], k = 1
输出:5
解释:选择下标 1 ,nums 变为 [4,-2,3] 。
示例 2:
输入:nums = [3,-1,0,2], k = 3
输出:6
解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。
示例 3:
输入:nums = [2,-3,-1,5,-4], k = 2
输出:13
解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。
个人思路:
- 先将数组排序,然后按起始下标对负数进行反转
- 负数个数 > k,正常反转完k个,之后遍历剩下的元素即可
- 负数个数 < k,反转完所有负数后,看看剩余次数为奇数还是偶数
- 若为偶数,就不需要再反转了
- 若为奇数,只需要再反转一个正数就行,比较一下此刻元素和上一个元素的绝对值,反转较小的那个即可
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
int result = 0;
Arrays.sort(nums);
int i = 0;
for (; i < k && i < nums.length; i++) {
if (nums[i] >= 0)
break;
nums[i] = Math.abs(nums[i]);
result += nums[i];
}
if ((k - i) % 2 == 1) {//还剩奇数个反转
//1.数组已遍历完毕(必然是负数,即其绝对值最小)--->将最后一个反转
//2.1数组未遍历完毕,比较正负分界处两值绝对值,负数的绝对值较大
//2.2数组第一个数就是正数,分界处,正数绝对值大
if (i >= nums.length || i > 0 && nums[i] > nums[i - 1])
result -= 2 * nums[i - 1];
else nums[i] *= (-1);
}
//遍历剩余元素
for (; i < nums.length; i++) {
result += nums[i];
}
return result;
}
}
题解解析:
将数组按绝对值大小进行排序,从大的那一侧遍历,遇到负数就反转
若到末尾还有反转次数,则看次数是奇数还是偶数进行不同操作
奇数则反转最后一个数,偶数则不反转
134. 加油站
题目介绍:
在一条环路上有 n
个加油站,其中第 i
个加油站有汽油 gas[i]
升。
你有一辆油箱容量无限的的汽车,从第 i
个加油站开往第 i+1
个加油站需要消耗汽油 cost[i]
升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas
和 cost
,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1
。如果存在解,则 保证 它是 唯一 的。
示例 1:
输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
示例 2:
输入: gas = [2,3,4], cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。
个人思路:
首先,我们需要使用几个参数去记录一些关键信息
int result = 0;//记录出发的下标
int excess_sum = 0;//遍历全程后,剩下的油
int[] excess_gas = new int[gas.length];//记录每一处加油与油耗的差值
int[] sum_gas = new int[gas.length];//当前下标记录从某个点起至当前点所剩油
然后,我们通过一个for循环遍历整个过程,过程中不断刷新sum_gas,当其为负数时,重新确定出发点,继续循环过程
如果真的能有某个点能循环走完全程,那必然在遍历过程中就碰到;反过来,遍历结束的result不一定就是结果,我们还得看excess_sum是否为非负
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int result = 0;//记录出发的下标
int excess_sum = 0;//遍历全程后,剩下的油
int[] excess_gas = new int[gas.length];//记录每一处加油与油耗的差值
int[] sum_gas = new int[gas.length];//当前下标记录从某个点起至当前点所剩油
for (int i = 0; i < gas.length; i++) {
excess_gas[i] = gas[i] - cost[i];
sum_gas[i] = i == 0 ? excess_gas[i] : excess_gas[i] + sum_gas[i - 1];
excess_sum += excess_gas[i];
if (sum_gas[i] < 0) {
sum_gas[i] = 0;
result = i + 1;
}
}
return excess_sum >= 0 ? result : -1;
}
}
题解解析:
法一:一种很巧妙的方法,但并非贪心,了解即可
- 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
- 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
- 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点。反过来思考,正序遍历那个点能填平最小的负数,那后面必然可以走完全程,因为整体是够油的
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int curSum = 0;
int min = INT_MAX; // 从起点出发,油箱里的油量最小值
for (int i = 0; i < gas.size(); i++) {
int rest = gas[i] - cost[i];
curSum += rest;
if (curSum < min) {
min = curSum;
}
}
if (curSum < 0) return -1; // 情况1
if (min >= 0) return 0; // 情况2
// 情况3
for (int i = gas.size() - 1; i >= 0; i--) {
int rest = gas[i] - cost[i];
min += rest;
if (min >= 0) {
return i;
}
}
return -1;
}
};
法二:和我的想法类似,但是代码优化了不少
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int result = 0;//记录出发的下标
int excess_sum = 0;//遍历全程后,剩下的油
int curSum = 0;//当前下标记录从某个点起至当前点所剩油
for (int i = 0; i < gas.length; i++) {
curSum += gas[i] - cost[i];
excess_sum += gas[i] - cost[i];
if (curSum < 0) {
curSum = 0;
result = i + 1;
}
}
return excess_sum >= 0 ? result : -1;
}
}
135. 分发糖果
题目介绍:
n
个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1
个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
个人思路:
我的想法是标记从起点开始,遍历标记每个点的相对上下位置,下标0处为0,大的就较前一个数+1,小的就-1,
但是相等的处理上较为复杂,想到反单调变化(),但遇到连续很多个孩子一样成绩也要再考虑,时间原因直接看题解了
题解解析:
注意:题目说的是,比相邻的大,就要比他得到的糖果更多,如果相同就没有这个限制。相邻这个条件我们可以分成两边来考虑
我们可以先比较一边再去比较另一边,如果一次遍历同时比较两边一定会失衡。
局部最优:只要右边孩子比左边孩子大,右边孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左孩子更多的糖果。
步骤:
- 我们先从首位起正序遍历,向右比较,得出仅一侧的全局最优,但此时不满足向左比较的条件
- 再从尾部起逆序遍历,向左比较,也能逐个得到结果,过程中我们要保留与正序遍历的结果相比中较大的结果(其能满足相邻比较的条件)
向左比较不能从首部遍历,不然会重复第一步的过程,没有意义
class Solution {
public int candy(int[] ratings) {
int[] candy = new int[ratings.length];
for (int i = 0; i < candy.length; i++) {
if (i > 0 && ratings[i] > ratings[i - 1])
candy[i] = candy[i - 1] + 1;
else candy[i] = 1;
}
for (int i = candy.length - 1; i > 0; i--) {
if (ratings[i] < ratings[i - 1])
candy[i - 1] = Integer.max(candy[i] + 1, candy[i - 1]);
}
int nums = 0;
for (int i = 0; i < candy.length; i++) {
nums += candy[i];
}
return nums;
}
}