LeetCode55 跳跃游戏(带扩展)

22 阅读3分钟

leetcode.cn/problems/ju…

image.png

解法一:贪心策略

贪心选择性质就是说能够通过局部最优解直接推导出全局最优解

题意可以理解为:请问通过题目中的跳跃规则,最多能跳多远?如果能够越过最后一格,返回 true,否则返回 false。

只需要遍历所有元素,记录当前能到达的最远位置,并判断能否越过最后一格就行了。具体步骤:

  • 从左到右遍历 nums,同时维护当前能跳到的最远位置fastest,初始值为 0。
  • 如果发现某个下标 i > fastest,说明跳不到当前下标位置,返回false
  • 否则,比较fastest和 i + nums[i]的大小,取较大者更新fastest
  • 如果最终循环结束,fastest越过了最后一个下标位置,返回true,否则返回false。
func canJump(nums []int) bool {
    fastest := 0
    for i, jump := range nums{
        if i > fastest{ // 无法到达下标 i
            return false
        }
        fastest = max(fastest, i+jump)
    }
    return fastest >= len(nums)-1
}

func max(a, b int) int{
    if a > b{
        return a
    }
    return b
}

另一种写法

func canJump(nums []int) bool {
    farthest := 0
    for i := 0; i<len(nums)-1; i++{ // 总共能跳的次数是len-1
        // 不断更新能跳到的最远位置
        farthest = max(farthest, i + nums[i])
        // 中间可能碰到0,卡住了,那就不能再继续往后跳了,肯定不能到达最后一格
        if farthest == i{
            return false
        }
    }
    // 遍历完,判断farthest能否越过最后一格
    return farthest >= len(nums)-1
}

func max(a, b int) int{
    if a > b{
        return a
    }
    return b
}
  • 时间复杂度:O(N)
  • 空间复杂度:O(1)

跳跃游戏 II

来看这道题的进阶版,leetcode.cn/problems/ju…

image.png 现在题意变成是:保证你一定可以跳到最后一格,请问你最少要跳多少次,才能跳过去?

思路一:贪心策略

不需要真的递归穷举出所有选择的具体结果来比较求最值,而只需要每次选择那个最有潜力的局部最优解,最终就能得到全局最优解

func jump(nums []int) int {
    farthest := 0 // 当前能够跳到的最远距离
    end := 0 // [i...end]表示可以选择的跳跃步数,end为可跳跃区间的右边界
    steps := 0 // 记录总的跳跃步数
    for i := 0; i<len(nums)-1; i++{
        farthest = max(farthest, i + nums[i])
        if end == i{ // 已经到达了当前可跳区间的右边界,需要跳一步,扩大一下右边界
            steps++
            end = farthest // 直接跳到当前能够到的最远位置
        }
    }
    return steps
}

func max(a, b int) int{
    if a > b {
        return a
    }
    return b
}

思路二:抽象为建桥问题

我们换个思路分析这道题

image.png 显然,下一步我们应该选择右端点最大的那座桥,因为它能让你走得更远,从而保证我们最终需要修建的桥数更少 image.png

image.png

func jump(nums []int) int {
    curRight := 0 // 已建造桥的右端点
    nextRight := 0 // 可选择的下一座桥的右端点最大值
    res := 0 // 修建的桥数
    for i, v := range nums{
        if i == len(nums) -1 { // 已经走到了终点,无需再建桥
            break
        }
        nextRight = max(nextRight, i+v) // 遍历过程中更新可修建桥的最大右端点
        if i == curRight{ // 走到当前桥的尽头,需要修建下一座桥,选择右端点最大的
            curRight = nextRight // 建桥后,右端点最远更新为nextRight
            res++
        }
    }
    return res
}

func max(a, b int) int{
    if a > b {
        return a
    }
    return b
}

小结

image.png