1005.K次取反后最大化的数组和
题目链接:1005.K次取反后最大化的数组和
文档讲解:代码随想录/K次取反后最大化的数组和
视频讲解:视频讲解-K次取反后最大化的数组和
状态:已完成(1遍)
解题过程
看到题目的第一想法
这道题我的想法是:首先当数组里有负数的时候,每次让最小的负数取反;如果数组里没有负数了但是有0,那就意味着数组里除了正数就是0,那么每次取反都让0来,直接输出当前数组的和;如果数组里只有正数,那么进行判断:如果k是偶数,那取反k次不会改变当前数组和,所以直接输出;如果k是奇数,那每次取反只让最小的那个数取反,所以输出数组和在减去当前数组最小的数*2。
手搓代码如下:
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var largestSumAfterKNegations = function(nums, k) {
let newNums = nums.sort((a,b)=>a-b);
let i = 0;
while(k){
if(newNums[i]<0){
//最小的数字如果小于0,取反之后看下一个数字
newNums[i] = -newNums[i];
i++;
k--;
}else if(newNums[i] == 0){
//k还没走完,数组里最小的数已经是0了
return newNums.reduce((sum,cur)=>sum+cur,0);
}else{
//k还没走完,整个数组中已经全是大于0的数了
if(k%2 == 0){
//k为偶数
return newNums.reduce((sum,cur)=>sum+cur,0);
}else{
let newNewNums = newNums.sort((a,b)=>a-b);
return newNewNums.reduce((sum,cur)=>sum+cur,-2*newNewNums[0]);
}
}
}
//k走完了数组里还有负数
return newNums.reduce((sum,cur)=>sum+cur,0);
};
提交没有问题,哈哈,难得贪心自己想出来一次。
看完代码随想录之后的想法
代码随想录的思路是将整个数组按绝对值大小从大到小排列。然后对整个新数组进行遍历,在保证k还有剩的情况下遇到一个负值就给他取反。最后如果全部负值都取反完毕则将绝对值最小的那个值不断取反,最后得到结果。
讲解代码如下:
var largestSumAfterKNegations = function(nums, k) {
nums.sort((a,b) => Math.abs(b) - Math.abs(a))
for(let i = 0 ;i < nums.length; i++){
if(nums[i] < 0 && k > 0){
nums[i] = - nums[i];
k--;
}
}
// 若k还大于0,则寻找最小的数进行不断取反
while( k > 0 ){
nums[nums.length-1] = - nums[nums.length-1]
k--;
}
// 使用箭头函数的隐式返回值时,需使用简写省略花括号,否则要在 a + b 前加上 return
return nums.reduce((a, b) => a + b)
};
总结
贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。
局部最优可以推出全局最优。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。
那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。
按照文字讲解中的这个思路的话,我认为我的过程也很符合,但是在处理剩余的k时需要额外进行一次排序。
134. 加油站
题目链接:134. 加油站
文档讲解:代码随想录/加油站
视频讲解:视频讲解-加油站
状态:已完成(1遍)
解题过程
看到题目的第一想法
这题我的想法是首先你得保证出发的加油站的gas要大于cost,这样才能跑得起来;第二点就是跑起来之后油箱里的油但凡小于0,就说明行不通,就得看下一个出发点。
手搓代码如下:
/**
* @param {number[]} gas
* @param {number[]} cost
* @return {number}
*/
var canCompleteCircuit = function (gas, cost) {
let len = gas.length;
for (let i = 0; i < len; i++) {
if (gas[i] > cost[i]) {
//此加油站的油量大于cost才能出发
let startIndex = i;
let curGas = gas[i] - cost[i];
while (curGas >= 0 && ++startIndex != i) {
if (startIndex == len && i != 0) {
//不是从开头出发的就回到开头
startIndex = -1;
continue;
}
//只有在一个加油站获得了油且扣去当前的cost之后还大于等于0才能继续走
curGas += gas[startIndex] - cost[startIndex];
}
if (curGas >= 0) return i;
}
}
return -1;
};
提交只有34/40。debug了发现对于从i=0出发的缺少了一个if判断,补上之后:
/**
* @param {number[]} gas
* @param {number[]} cost
* @return {number}
*/
var canCompleteCircuit = function (gas, cost) {
let len = gas.length;
for (let i = 0; i < len; i++) {
if (gas[i] > cost[i]) {
//此加油站的油量大于cost才能出发
let startIndex = i;
let curGas = gas[i] - cost[i];
while (curGas >= 0 && ++startIndex != i) {
if (startIndex == len && i != 0) {
//不是从开头出发的就回到开头
startIndex = -1;
continue;
}else if(startIndex == len && i == 0){
return i;
}
//只有在一个加油站获得了油且扣去当前的cost之后还大于等于0才能继续走
curGas += gas[startIndex] - cost[startIndex];
}
if (curGas >= 0) return i;
}
}
return -1;
};
提交39/40.。。。唯一一个错误例子是一个长度为1且gas和cost相等的,当我将for循环的判断条件加上了一个等于号直接超时。我才想到我这个方法可能复杂度终究是太高了。
看完代码随想录之后的想法
他的暴力解法思路和我一样,但是区别就在跑到数组末尾的时候如何更新位置到数组起点,真是学到了。
讲解代码如下:
/**
* @param {number[]} gas
* @param {number[]} cost
* @return {number}
*/
var canCompleteCircuit = function(gas, cost) {
for(let i = 0; i < cost.length; i++) {
let rest = gas[i] - cost[i] //记录剩余油量
// 以i为起点行驶一圈,index为下一个目的地
let index = (i + 1) % cost.length
while(rest > 0 && index !== i) {
rest += gas[index] - cost[index]
index = (index + 1) % cost.length
}
if(rest >= 0 && index === i) return i
}
return -1
};
同时还有很关键的一点,如果在遍历的过程中当前油箱里的油一直大于等于0,在碰到一个地方的时候变成小于0,那么就意味着从起点到这一个加油站之间的任意一个加油站出发都会在这里开不下去。所以只能够在这个加油站之后的加油站出发。
贪心算法代码如下:
/**
* @param {number[]} gas
* @param {number[]} cost
* @return {number}
*/
var canCompleteCircuit = function(gas, cost) {
const gasLen = gas.length
let start = 0
let curSum = 0
let totalSum = 0
for(let i = 0; i < gasLen; i++) {
curSum += gas[i] - cost[i]
totalSum += gas[i] - cost[i]
if(curSum < 0) {
curSum = 0
start = i + 1
}
}
if(totalSum < 0) return -1
return start
};
总结
学到了暴力解法一个转圈问题和while语句相匹配的处理索引的方法:
let index = (i + 1) % cost.length
135. 分发糖果
题目链接:135. 分发糖果
文档讲解:代码随想录/分发糖果
视频讲解:视频讲解-分发糖果
状态:已完成(1遍)
解题过程
看到题目的第一想法
想不出来bro。
看完代码随想录之后的想法
确实在思考的时候就觉得对于某一个小孩应该分发的糖果数量与左右两边都受影响,不知道改如何是好。
先确定右边评分大于左边的情况(也就是从前向后遍历)
此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
局部最优可以推出全局最优。
再确定左孩子大于右孩子的情况(从后向前遍历)
遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?
因为 rating[5]与rating[4]的比较 要利用上 rating[5]与rating[6]的比较结果,所以 要从后向前遍历。
讲解代码如下:
/**
* @param {number[]} ratings
* @return {number}
*/
var candy = function(ratings) {
let candys = new Array(ratings.length).fill(1)
for(let i = 1; i < ratings.length; i++) {
if(ratings[i] > ratings[i - 1]) {
candys[i] = candys[i - 1] + 1
}
}
for(let i = ratings.length - 2; i >= 0; i--) {
if(ratings[i] > ratings[i + 1]) {
candys[i] = Math.max(candys[i], candys[i + 1] + 1)
}
}
let count = candys.reduce((a, b) => {
return a + b
})
return count
};
提交没有问题。
总结
这种值受到左右影响的情况下一定要先确定一边,再确定另一边。