跳跃游戏 II:当前窗右端才结算一步
窗口里每个落点都用同一跳到达。先在窗内更新下一跳最远,扫到窗的右端再 steps+1,把边界换成 nextFar。
维护 steps、curEnd、nextFar。扫到 i 先用 i+nums[i] 抬 nextFar;当 i 走到 curEnd,当前这一跳的覆盖用尽,steps 加一,curEnd 换成 nextFar。循环只走到倒数第二个下标,避免已经站在终点还多算一跳。时间 O(n),空间 O(1)。
这是 LeetCode 45. Jump Game II。大白话:数组 nums 的每个位置写着「从这里最多能往右跳几格」,你从下标 0 出发,每次跳 1 到 nums[i] 格,问跳到最后一个下标最少要几跳。题目保证一定能到。
主例 nums = [2, 3, 1, 1, 4]。从 0 跳 1 格落到下标 1,再从 1 跳 3 格落到下标 4,两跳。从 0 跳 2 格落到下标 2,再跳到 4,也是两跳。一跳到不了 4,因为 nums[0] 只有 2。答案是 2。
LC55 只问能不能到,记下全局最远即可。本题要最少跳数。缺的不是「最远能到哪」,而是「这一跳的覆盖在哪里结束」。每到一个位置就立刻跳,会把同一次跳跃里的候选落点拆成多次,步数偏大。正确的结算点是当前窗口的右端。
窗内更新最远,右端才加步
第一跳从下标 0 出发,能落到的最远是 nums[0],窗口是 [0, nums[0]]。窗口里每一个位置,都已经用 1 跳到达。从这些位置再起跳,能摸到的更远边界记作 nextFar。只有把窗口扫完,才知道第二跳最远能到哪,这时才把步数加一,并把当前窗口右端换成 nextFar。之后每一跳都是同一句话:窗内更新,右端结算。
三个量各管一件事。steps 是已经完整走完的跳数。curEnd 是当前这一跳覆盖的右端,i 还没走到它,步数就不能加。nextFar 是「假如现在再跳一次」能到的最远,它在窗内被每个 i+nums[i] 抬高。i 碰到 curEnd,才执行 steps++ 和 curEnd = nextFar。这是步数增加的唯一时机。
循环写成 i < n−1,不处理最后一个下标。已经站在终点,不必再跳;若最后一格碰巧等于 curEnd,再结算一次会把答案多加 1。只有一个元素时循环不进,steps 保持 0,人已经在终点。
用手走主例。开始 steps=0,curEnd=0,nextFar=0。i=0,0+2=2,nextFar=2。i 等于 curEnd,结算:steps=1,curEnd=2。演示第一帧停在「第 1 波覆盖到 2」、步数还是 0 的那一瞬;紧接着结算完成。i=1,1+3=4,nextFar=4,i 还没到 curEnd=2,不加步。演示第二帧:pos=1,steps=1,curEnd=2,nextFar=4。i=2,2+1=3,抬不过 4;i 等于 curEnd,再结算:steps=2,curEnd=4。演示第三帧覆盖已经含终点。i=3 仍小于 4,3+1=4,不触发结算。循环在 i=4 之前结束,答案 2。
为什么这样最少?第 k 跳覆盖的是一个连续窗口,窗口内任何落点都是 k 跳到达,不存在「跳到窗内某处却用了更多跳」的更优解。下一跳的最远被窗内所有起跳点的最大值唯一确定,没有必要在窗内提前结算。BFS 按层扩张得到的是同一组层,只是用两个指针代替了队列。
结算点curEnd 是这一跳的右端,nextFar 是下一跳的右端。i 碰到 curEnd 才加步。每到一个格子就 steps++,主例会在下标 1 处被加成 2,再在下标 2 加成 3,错成 3。
七行 Go:波次计数
func jump(nums []int) int {steps, curEnd, nextFar := 0, 0, 0for i := 0; i < len(nums)-1; i++ {if i+nums[i] > nextFar { nextFar = i + nums[i] }if i == curEnd {steps++curEnd = nextFar}}return steps}
1先抬 nextFar,再问 i 是否到了 curEnd。主例 i=0 先把 nextFar 写成 2,再结算成 steps=1、curEnd=2。
2i == curEnd 是加步的唯一入口。主例第二次结算发生在 i=2,不是 i=1。
3循环上界是 n−1。主例不处理下标 4;处理了会在 curEnd 已是 4 时多加一跳。
4单元素数组一次循环都不进,返回 0。
总结
窗内更新 nextFar,碰到 curEnd 才 steps+1。主例在 i=0 与 i=2 各结算一次,答案 2。
- LC55 记全局最远就够。本题缺的是「这一跳的覆盖在哪结束」,结算点必须是窗的右端。
- 主例第一跳覆盖到 2,窗内 3 把下一跳抬到 4;第二跳覆盖终点。
- 循环不要包含最后一个下标,否则站在终点还会再加一步。