题目描述
思路解析动画文字版
记住两句:最大「移一端、丢小端缝、数空格」;最小「滑长 n 窗口、数窗内最多石子、特例留空位算 2 步」。后面每帧都在套它。
先看原始输入,6 颗石子位置乱序排列。所有套路都建立在「排好序」之上,所以第一步永远是排序。
排好序后变成 2、3、5、6、8、11。最左是 2、最右是 11,整段从 2 到 11 一共 10 个位置,里面只占了 6 颗。接下来先算最大、再算最小。
先算最大步数。整段 10 个位置里有 4 个空格。最理想是每一步只填一个空格、慢慢挪。但第一步必须把一个端点往里搬,会放弃它那一侧的端缝,所以要在两种放弃里挑划算的。
第一种:第一步动最左的 2,等于放弃左端缝,往后所有石子都挤进 3 到 11 这段。这段有 4 个空格,每步填一个,最多走 4 步。
第二种:第一步动最右的 11,等于放弃右端缝,石子挤进 2 到 8 这段。这段只有 2 个空格,最多走 2 步。
两种放弃里挑空格多的:4 和 2 取大,最大步数就是 4。直觉上就是「丢掉较小的那条端缝,剩下的空格能填多少步就走多少步」。
再算最小。终局是 6 个挨着的位置。我们拿一个宽度不超过 6 的窗口在数轴上滑,窗口里已经就位的石子越多,要从外面搬进来的就越少。右指针 j 往右扩,一旦窗口宽度超过 6,左指针 i 就跟着右移。
右指针扩到位置 2,窗口宽度 1,还在 6 以内,左指针不动。
窗内已就位 1 颗,外面还有 5 颗要搬进来,本窗需要 5 步。比之前更少,最少刷新成 5。
右指针扩到位置 3,窗口宽度 2,还在 6 以内,左指针不动。
窗内已就位 2 颗,外面还有 4 颗要搬进来,本窗需要 4 步。比之前更少,最少刷新成 4。
右指针扩到位置 5,窗口宽度 4,还在 6 以内,左指针不动。
窗内已就位 3 颗,外面还有 3 颗要搬进来,本窗需要 3 步。比之前更少,最少刷新成 3。
右指针扩到位置 6,窗口宽度 5,还在 6 以内,左指针不动。
窗内已就位 4 颗,外面还有 2 颗要搬进来,本窗需要 2 步。比之前更少,最少刷新成 2。
右指针扩到位置 8,宽度一度超过 6,左指针往右移了 1 步,缩到位置 3,让窗口宽度回到 6,控制在 6 以内。
窗内已就位 4 颗,外面还有 2 颗要搬进来,本窗需要 2 步。没比当前最少 2 更优,继续滑。
右指针扩到位置 11,宽度一度超过 6,左指针往右移了 2 步,缩到位置 6,让窗口宽度回到 6,控制在 6 以内。
窗内已就位 3 颗,外面还有 3 颗要搬进来,本窗需要 3 步。没比当前最少 2 更优,继续滑。
滑完一遍,最划算的窗口已就位 4 颗,只要再搬 2 颗,最少就是 2 步。配上前面算的最大 4 步,主例答案是 [2, 4]。
单独看这个最容易错的特例:1、2、3、4 已经连成一片,只有 9 落在远处。窗口里能装下 4 颗连续石子,看着像只差 1 颗、1 步就完事,真的吗?
朴素地套公式:窗口里有 4 颗,n 是 5,5 减 4 等于 1,好像 1 步就行。问题出在这个 1 步根本走不通,下一帧看为什么。
想 1 步收尾,只能把端点 9 挪到位置 5,凑成 1、2、3、4、5。可规则要求端点挪完不能还是端点,而 5 恰好是新的最右端,这步违规。所以 1 步走不通。
正确走法要 2 步:先把最左的 1 挪到 6,变成 2、3、4、6、9;再把端点 9 挪到 5,凑成 2、3、4、5、6 连续收工。所以「n-1 已连续、只差一个空位、最后一颗在远处」这个特例,最少是 2 步,公式里要单独判它。
边界:已连续输出 0;n=3 看间隙——没有恰好长 2 的间隙就 1 步收不了尾、最少 2 步;记牢「几乎排好」的特例也算 2 步。
面试两问:最大只有两种端缝取舍;窗口宽度上限 n 来自终局的 n 个连续位置。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class Solution: def numMovesStonesII(self, stones: List[int]) -> List[int]: stones.sort() mi = n = len(stones) mx = max(stones[-1] - stones[1] + 1, stones[-2] - stones[0] + 1) - (n - 1) i = 0 for j, x in enumerate(stones): while x - stones[i] + 1 > n: i += 1 if j - i + 1 == n - 1 and x - stones[i] == n - 2: mi = min(mi, 2) else: mi = min(mi, n - (j - i + 1)) return [mi, mx]复杂度
- 时间:O(n log n),瓶颈是排序;之后的最大计算 O(1)、滑动窗口一遍 O(n)
- 空间:O(1) 起,只用 i、j、mi、mx 几个变量;排序内部栈 C++/Java 约 O(log n)、Python 最坏 O(n)
易错点
面试追问把动画讲成自己的话
追问最大步数为什么只看两个候选窗口?
追问为什么滑动窗口的宽度上限取 n 而不是别的?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
单字符重复子串的最大长度
LeetCode 1156 · 中等 · 沿着 双指针套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题