55. 跳跃游戏:给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
方法一
思路:
- 需要判断是否能够到达最后一个下标,从正面思考的话需要判断的次数有点多,所以我们反向思考,从最后一个索引往前推
- 设置起始值
j
为数组最后一个下标,遍历数组比较 当前值nums[i]
和当前索引i
跳跃至目标索引j
的值,如果当前值nums[i] - needSteps >= 0
,则说明当前索引是能够跳至目标索引j
的,那我们只需要继续往前能够跳跃到当前索引,就肯定可以跳到最后一个下标了,因为当前索引比最后一个下标要小,所需的跳跃步数也更少,所以我们更新目标索引j
到当前索引i
- 更新了
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
};
方法二( 推荐 官方题解 )
思路:
- 遍历数组,当满足
i <= jump
条件时,说明索引在可跳跃的区间内,将当前可跳跃到的最大索引i + nums[i]
与之前可跳跃区间中更大的值,重新赋值可跳跃的最远区间 - 可跳跃到的最远区间,大于或等于 结束索引时,返回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++
}
}
};
方法二
思路:
- 分别定义三个值为 边界点
end
、最大下标位置maxPosition
、跳跃次数steps
- 最大边界点肯定不会是最后一个值,所以遍历到
length - 1
即可 - 以 最大下标位置、当前最大下标位置中的 最大值 更新为 最大下标位置
- 达到 边界点 时,更新 边界 到最大位置下标,跳跃次数
+1
,这一步的理解最为重要,从左到右开始跳跃,maxPosition = Math.max(maxPosition, i + nums[i])
获取边界内最大值,到达边界时跳跃次数+1
,并end = maxPosition
赋值新的边界值,即从上一个 边界区间 内能跳跃到的 最大值 变为新的边界,因为题中说明测试用例一定能跳跃到n-1
,所以不断的用 最大值 替换 边界值,到边界就更新跳跃次数即可,由于需要求的是 最小跳跃次数,所以最后一个最大值 必定要能覆盖到结束索引,会不会存在,不在边界内才覆盖到结尾的可能呢?假设最后一个变为i
,在i
左边出现的最大值覆盖到结束的话,i == end
次数+1
能返回正确的最小跳跃次数,在i
的右边出现最大值覆盖到结束的话,那说明前一个区间内没有覆盖到,那又会生成新的区间,那它还是在区间内,所以最后一个最大值一定是在区间内的 - 遍历结束后,返回跳跃次数即可
总结:到达边界点时跳跃次数+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
};