leetCode 编号55和45

10 阅读5分钟

55. 跳跃游戏:给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 

方法一
思路:
  1. 需要判断是否能够到达最后一个下标,从正面思考的话需要判断的次数有点多,所以我们反向思考,从最后一个索引往前推
  2. 设置起始值j为数组最后一个下标,遍历数组比较 当前值nums[i]和当前索引i跳跃至目标索引j的值,如果当前值nums[i] - needSteps >= 0,则说明当前索引是能够跳至目标索引j的,那我们只需要继续往前能够跳跃到当前索引,就肯定可以跳到最后一个下标了,因为当前索引比最后一个下标要小,所需的跳跃步数也更少,所以我们更新目标索引j到当前索引i
  3. 更新了j之后,我们继续往前遍历,直至i = 0,此时判断能否跳跃至当前目标索引j,能跳跃到则说明能够跳跃到最后一个下标,否则则不能

总结:就是把从 第一个下标 跳跃到 最后一个下标 的问题,先反向思维为从 最后一个下标 返回到 第一个下标 ,再将 起点下标 不断往前移动到,离 当前起点下标 最近且能跳跃成功的 ,直至最后判断能否跳跃回 第一个下标

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canJump = function(nums) {
    let len = nums.length
    // 需要跳到的目标索引
    let j = len - 1
    for(let i = len - 2; i > -1; i--) {
        // 从索引i跳跃至j需要的步数
        let needSteps = j - i
        // 如果当前值 - 需要跳跃的步数 >= 0 则说明i一定能跳跃到当前的目标索引j处 更新j为i
        if(nums[i] - needSteps >= 0) {
            j = i
        }
        // 当前值为0时 若无法跳跃到当前的目标索引j 则返回false
        if(i === 0 && nums[i] - needSteps < 0) {
            return false
        } 
    }
    return true
};
方法二( 推荐 官方题解
思路:
  1. 遍历数组,当满足i <= jump条件时,说明索引在可跳跃的区间内,将当前可跳跃到的最大索引i + nums[i]与之前可跳跃区间中更大的值,重新赋值可跳跃的最远区间
  2. 可跳跃到的最远区间,大于或等于 结束索引时,返回true,否则返回false
/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canJump = function(nums) {
    let len = nums.length
    let jump = 0
    for(let i = 0; i < len; i++) {
        if(i <= jump) {
            jump = Math.max(i + nums[i], jump)
            if(jump >= len - 1) {
                return true
            }
        }
    }
    return false
};

45. 跳跃游戏 II:给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i] 
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

方法一
/**
 * @param {number[]} nums
 * @return {number}
 */
var jump = function (nums) {
    let len = nums.length
    let jump = 0
    let jump1 = 0
    let jumpCount = 0
    // 数组长度为1的直接返回
    if (len == 1) { return 0 }
    for (let i = 0; i < len; i++) {
        // 如果当前索引+可跳跃次数 >= 可跳跃到的索引值
        if (i + nums[i] >= jump) {
            // 更新可跳跃到的索引值
            jump = i + nums[i]
            // 跳跃次数+1
            jumpCount++
            // 如果可跳跃到的索引值 > 结束索引值 返回当前跳跃次数
            if (jump >= len - 1) {
                return jumpCount
            }
            // 如果可跳跃到的索引值 <= 结束索引值 则不跳跃到该索引处 跳跃次数 -1
            jumpCount--
        }
        // 如果到了能跳跃到的区间的极限值 还没返回值 则更新可跳跃次数jump1到此区间最大值处 并且跳跃次数+1
        if (i == jump1) {
            jump1 = jump
            jumpCount++
        }
    }
};
方法二
思路:
  1. 分别定义三个值为 边界点end最大下标位置maxPosition跳跃次数steps
  2. 最大边界点肯定不会是最后一个值,所以遍历到length - 1即可
  3. 最大下标位置当前最大下标位置中的 最大值 更新为 最大下标位置
  4. 达到 边界点 时,更新 边界 到最大位置下标,跳跃次数+1,这一步的理解最为重要,从左到右开始跳跃,maxPosition = Math.max(maxPosition, i + nums[i])获取边界内最大值,到达边界时跳跃次数+1,并end = maxPosition赋值新的边界值,即从上一个 边界区间 内能跳跃到的 最大值 变为新的边界,因为题中说明测试用例一定能跳跃到n-1,所以不断的用 最大值 替换 边界值,到边界就更新跳跃次数即可,由于需要求的是 最小跳跃次数,所以最后一个最大值 必定要能覆盖到结束索引,会不会存在,不在边界内才覆盖到结尾的可能呢?假设最后一个变为i,在i左边出现的最大值覆盖到结束的话,i == end次数+1能返回正确的最小跳跃次数,在i的右边出现最大值覆盖到结束的话,那说明前一个区间内没有覆盖到,那又会生成新的区间,那它还是在区间内,所以最后一个最大值一定是在区间内的
  5. 遍历结束后,返回跳跃次数即可

总结:到达边界点时跳跃次数+1,更新边界点到最大下标位置

/**
 * @param {number[]} nums
 * @return {number}
 */
var jump = function (nums) {
    let len = nums.length
    let end = 0;
    let maxPosition = 0;
    let steps = 0;
    for (let i = 0; i < len - 1; i++) {
        maxPosition = Math.max(maxPosition, i + nums[i])
        if (i == end) {
            end = maxPosition
            steps++
        }
    }
    return steps
};