代码随想录算法训练营第三十三天 | 1005. K 次取反后最大化的数组和、134. 加油站、135. 分发糖果

47 阅读4分钟

1005. K 次取反后最大化的数组和

链接

文章链接

题目链接

第一想法

由于是求最大值,所以我的想法是尽可能让绝对值大的负数先变成正的,这样出来的结果才能保证是最大的,所以出现了三种情况:

  1. 数组有负数而k>0的情况:让绝对值大的负数变为正
  2. 数组有负数且k<=0,此时只能让结果值加上负数的值
  3. 数组无负数且k>0,这就有两种情况,一为k为偶数则不影响结果值,二为k为奇数,就让结果值减去2倍的min值就可以了
function largestSumAfterKNegations(nums: number[], k: number): number {
    let res: number = 0 //结果值
    let min: number = Number.MAX_VALUE //存储最小值 用于数组无负数且k为奇数
    nums.sort((a, b) => a - b)//排序 帮助我们让绝对值大的负数先变成正的
    for (let i = 0; i < nums.length; i++) {
        if (nums[i] < 0 && k > 0) {//情况一
            res -= nums[i]
            k--
            min = Math.min(min, Math.abs(nums[i]))
        } else {
            if (k <= 0) { //情况二
                res += nums[i]
            } else {//情况三
                res += nums[i]
                min = Math.min(min, Math.abs(nums[i]))
            }
        }
    }
    if (k % 2 == 0 || min == 0) return res
    return res - 2 * min
};

看完文章的想法

文章的想法和我的核心是一样的,不过文章是按照绝对值的大小进行排序的,而且是从大到小,那么最后一个一定是绝对值最小的,如果遍历完k还是大于0的话,那么就直接减去2倍的最后一个元素就可以了,代码如下:

function largestSumAfterKNegations(nums: number[], k: number): number {
    let res: number = 0
    nums.sort((a, b) => Math.abs(b) - Math.abs(a))
    for (let i = 0; i < nums.length; i++) {
        if (nums[i] < 0 && k > 0) {
            res -= nums[i]
            k--
        } else res += nums[i]
    }
    console.log(nums, k, res)
    if (k % 2 === 1) return res - 2 * Math.abs(nums[nums.length - 1])
    return res
};

思考

这道题其实是比较简单的,但是还是要学习贪心算法的思路,找出局部最优之后看看能不能推出全局最优,我的想法是正确的,但是没有完全想到,想到了排序,但是没想到用绝对值排序会更简单.

134. 加油站

链接

题目链接

文章链接

第一想法

第一想法是从第一个加油站开始,分别计算加油站的油量总和和消耗总和,直到油量总和大于等于消耗总和,同时判断gas[i-1]是否大于cost[i-1],如果大于就返回i-1,否则返回i,但是测试gas=[5,1,2,3,4],cost=[4,4,1,5,1]证明了我的想法是错误的,想了30分钟,没啥思路,所以决定看文章了.

看完文章的想法

看完文章后,我发现我的贪心算法的学习是任重而道远的,文章用了三种方法,这里详细介绍两种方法,第一种是暴力,通过每个起点来模拟是否能转一圈,代码如下:

function canCompleteCircuit(gas: number[], cost: number[]): number {
    for (let i = 0; i < gas.length; i++) {
        let rest: number = gas[i] - cost[i]//剩余油量
        let index: number = (i + 1) % gas.length
        while (rest > 0 && index != i) {//模拟跑一圈
            rest += gas[index] - cost[index]
            index = (index + 1) % gas.length
        }
        if (index == i && rest >= 0) return i
    }
    return -1
};

第二种方法就是贪心,利用每跑完一个加油站的剩余油量的和看看是不是为正,如果不为正,只能从下一个加油站开始算(如图)

image.png

同时记录总共的的剩余量,如果最后总共的剩余量小于0,则说明肯定不能跑完一圈,代码如下:

function canCompleteCircuit(gas: number[], cost: number[]): number {
    let rest: number = 0 //汽车跑一段的剩余量
    let total: number = 0 //全部的剩油量
    let index: number = 0 //开始的下标
    for (let i = 0; i < gas.length; i++) {
        rest += gas[i] - cost[i]
        total += gas[i] - cost[i]
        if (rest < 0) {//如果跑到i是剩油量为0 说明之前不能为起点
            index = i + 1 //之前不能为起点
            rest = 0 //从i+1开始作为起点开始跑
        }
    }
    if (total < 0 || index >= gas.length) return -1
    return index
};

思考

这道题没想到,对我来说,有点难想,暴力也没太想到,主要是在贪心算法中限制了我的想法,确实,这应该警醒我了.

135. 分发糖果

链接

题目链接

文章链接

第一想法

第一想法是先初始化一个数组arr表示每个人应该分到的糖果(初始都为1),从前往后,如果ratings[i]>ratings[i-1],则arr[i]糖果数为arr[i-1]+1,如果遇到ratings[i]<ratings[i-1]的情况 则需要把arr[i-1]=arr[i]+1,代码如下:

function candy(ratings: number[]): number {
    let arr: number[] = new Array(ratings.length).fill(1)//创建初始糖果数组
    let res: number = 0 //总共糖果数
    for (let i = 0; i < ratings.length; i++) {
        if (ratings[i] > ratings[i - 1]) arr[i] = arr[i - 1] + 1 //上坡 应对[1,2,3]这种情况
        let index: number = i
        while (ratings[index] < ratings[index - 1] && arr[index] >= arr[index - 1]) { //应对[3,2,1]这种情况  同时必须保证糖果数arr[index] >= arr[index - 1],因为如果是ratings=[1,2,3,1],而糖果数为[1,2,3,1],则不需要再给让ratings[2]=ratings[3]+1了
            arr[index - 1] = arr[index] + 1
            index--
        }
    }
    for (let i = 0; i < arr.length; i++) {
        res += arr[i]//求和
    }
    return res
};

看完文章的想法

文章的想法是和我一样的,但是比我的好理解,文章用了两个for循环分别搞定ratings[i] > ratings[i - 1]和ratings[i] > ratings[i + 1]的两种情况:

ratings[i] > ratings[i - 1]情况(从前往后): image.png ratings[i] > ratings[i + 1]情况(从后往前):

image.png 代码如下:

function candy(ratings: number[]): number {
    let arr: number[] = new Array(ratings.length).fill(1)
    let res: number = 0
    for (let i = 1; i < ratings.length; i++) {
        if (ratings[i] > ratings[i - 1]) arr[i] = arr[i - 1] + 1
    }
    for (let i = ratings.length - 2; i >= 0; i--) {
        if (ratings[i] > ratings[i + 1]) arr[i] = Math.max(arr[i + 1] + 1, arr[i])
    }
    for (let i = 0; i < arr.length; i++) {
        res += arr[i]
    }
    return res
};

思考

这道题的灵感在于每日任务的提示,了解到不应该只看一边,所以自己就逐渐想出来了,但是写的代码质量还是和文章中有差别的,这个要继续学习

今日总结

今日耗时3小时,我认为第二题最难,因为想法都不对,而一三题想法都是对的,虽然有所欠缺.看来还得继续努力