代码随想录算法训练营第三十二天 | 122. 买卖股票的最佳时机 II、55. 跳跃游戏、45. 跳跃游戏 II

45 阅读3分钟

122. 买卖股票的最佳时机 II

链接

题目链接

文章链接

第一想法

这道题和摆动序列这道题非常类似,由于昨天做过摆动序列,所以这道题很快就想到了股价就是折线图,每次在低点买入,高点卖出,代码如下:

function maxProfit(prices: number[]): number {
    let res: number = 0 //手机结果
    for (let i = 1; i < prices.length; i++) {
        if (prices[i] <= prices[i - 1]) continue //股价下降直接不购买股票
        let index = i - 1 //从index时 估计开始上升
        while (prices[i] >= prices[i - 1]) i++; //股票一直上升
        res += prices[i - 1] - prices[index] //这是prices[i]是低于prices[i-1],所以应该在prices[i-1]卖出 获利为prices[i - 1] - prices[index]
    }
    return res
};

看完文章的想法

文章的想法和我的完全不同,文章的比较好理解,例如第一天买入第三天卖出,则利润为prices[2]-prices[0],也就是可以看作为prices[2]-prices[1]+prices[1]-prices[0],也就说,我们可以看每两天的差值作为利润,也就是把整体的利润分解为每天耳朵利润,例如这张图:

image.png 所以说只需要手机每天利润为正的就可以,代码如下:

function maxProfit(prices: number[]): number {
    let res: number = 0
    for (let i = 1; i < prices.length; i++) {
        res += Math.max(0, prices[i] - prices[i - 1])
    }
    return res
};

思考

这道题的文章写法很奇妙,我是按照摆动序列的解法修改来解决这道题(只求上坡度的差值),而文章用的贪心算法更妙,因为1-3天的的股票获利可以看成1-2,2-3股票获利之和,这样子文章的写法就容易理解了,只求值为正的和就是正确的结果.

55. 跳跃游戏

链接

题目链接

文章链接

第一想法

这道题要求是判断能否调到最后一个下标的位置,刚开始没有想法,但是想了一会发现我可以看看能否到达每个位置到达的最远位置,如果能达到的最远位置大于数组最后一个位置,则肯定能达到,代码如下:

function canJump(nums: number[]): boolean {
    let max: number = 0//最大可以跳哪
    for (let i = 0; i < nums.length; i++) {
        if (max < i) return false
        max = Math.max(max, nums[i] + i)
    }
    return true
};

看完文章后的想法

文章的想法和我的完全一致,都是通过最远到达的位置(查看能否覆盖),例如这张图:

image.png 我刚刚写的是负向代码,也可以正向解决这个问题,代码如下:

function canJump(nums: number[]): boolean {
    let max: number = 0//最大可以跳哪
    for (let i = 0; i <= max; i++) { //这里的是要<=max表示最远能跳到哪,不能是nums.length 负责最后一个元素一定能到达 因为max必定可以为nums[nums.length-1].
        max = Math.max(max, nums[i] + i)
        if (max >= nums.length - 1) return true
    }
    return false
};

思考

这道题想了一会发现有了思路,表示很惊喜,之后看完文章后,文章的写法确实考虑的更多,我的那种写法是直接排除了最后一个元素的if判断(if (max < i) return false),正好是正确的解法,但是当初我是没想到的.

45. 跳跃游戏 II

链接

文章链接

题目链接

第一想法

我的想法是模拟,尝试用递归模拟跳跃,但是发现代码是超时的,代码如下:

//以下方法是超时的
function jump(nums: number[]): number {
    let min: number = Number.MAX_SAFE_INTEGER
    let n: number = nums.length
    const foo = (num: number, count: number) => {
        if (num >= n - 1) {
            min = Math.min(min, count)
            return
        }
        for (let i = 1; i <= nums[num]; i++) {
            foo(i + num, count + 1)
        }
    }
    foo(0, 0)
    return min
};

之后又换了一种写法,思路为每跳跃一次就通过记录比较跳跃到当前的最最小次数,所以第一层for来模拟从当前开始跳,里面的循环模拟跳的位置:

function jump(nums: number[]): number {
    let arr: number[] = new Array(nums.length).fill(nums.length)
    arr[0] = 0
    for (let i = 0; i < nums.length; i++) {
        for (let j = 1; j <= nums[i]; j++) {
            if (i + j > nums.length - 1) continue
            arr[i + j] = Math.min(arr[i + j], arr[i] + 1)//比较得出调到当前位置的最小次数
        }
    }
    return arr[arr.length - 1]
};

看完文章的想法

看完文章后的想法发现,如果要我自己想我是真的想不到,这道题居然还是可以用最大覆盖范围来解决,不过这回是需要两个:一个为当前的最大范围,另一个为下一步的最大范围,这样,如果下一步的最大范围到达nums的最后一个元素,则返回步数,详细解释可以看这张图:

image.png 代码如下:

function jump(nums: number[]): number {
    let curr: number = 0 //当前覆盖范围
    let next: number = 0 //下一步覆盖范围
    let step: number = 0 //步数
    if (nums.length == 1) return 0
    for (let i = 0; i < nums.length; i++) {
        next = Math.max(next, nums[i] + i) //下一步的最大覆盖范围
        if (curr === i) { //达到当前覆盖范围时
            step++  //下一步就要走到下一步的最大范围
            curr = next//更新当前最大范围
            if (curr >= nums.length - 1) return step //当前最大范围达到最后一个元素直接返回step
        }
    }
    return step
};

思考

这道题确实没想到用最大覆盖范围还能做,这个需要能想清楚当前最大最大覆盖范围时可以移步到达的,通过当前最大覆盖范围和下一步最大覆盖范围进行迭代来完成的,每进行一次迭代(下一步覆盖最大范围赋值给当前覆盖最大范围),步数就需要加一,直到可以最大覆盖范围包括最后一个元素时截止.

今日总结

今天耗时2.5小时,三道都是不同类型的贪心算法,前两道都是自己想到的,但是最后一题的贪心是没想到,贪心的学习还要继续努力.