
把字符串想象成一排小格子,每格一个字母。
有一辆"窗口小火车"在这排格子上滑动,它有:
-
左车门:
left -
右车门:
right
规则:
-
火车载的乘客 = 当前子串里的字母
-
不能有重名的乘客(无重复字符)
做法:
-
right每次往右走一格,把新字母拉上车 -
如果发现车里已经有同名乘客了
→ 就从左边寻找到同名乘客,都"请下车"(left = max(left, char_map[cur] + 1))
-
每一次火车停下来的时候,记录车里乘客人数(窗口长度)
-
记住历史上最多的一次乘客数,就是答案
核心算法思路(滑动窗口)
-
用一个
set或dict记录窗口中出现的字符 -
right往右走遍历字符串 -
若
s[right]没出现在窗口中 → 直接加入 -
res = max(res, right - left)更新答案 -
若已经出现 → 移动
left到char_map中的同名索引;并从窗口中删字符
时间复杂度:O(n),每个字符最多进窗口一次、出窗口一次。
python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
char_map = {} #字典,用来存储每个字符最近出现的位置(索引)
left = 0
res = 0
for right in range(len(s)):
cur = s[right]
if cur in char_map:
res = max(res, right - left)
# 注意这一步,非常重要!排除了虽然存在,但在窗口之外的
left = max(left, char_map[cur] + 1)
char_map[cur] = right
return max(res, len(s) - left)

"开一个长度等于 p 的小窗,
每挪动一步,更新窗里的字母数量,
只要数量表和 p 一样,这个位置就是答案。"
核心算法思路(定长滑动窗口)
-
窗口长度固定为
k = len(p) -
用数组/字典统计
p中每个字母的次数need -
再用同样的结构维护窗口中每个字母的次数
window -
每次移动一格:
-
右边加一个字母
-
左边减一个字母(保证窗口长度始终为 k)
-
-
若
window == need,说明当前窗口是一个异位词 → 记录左端点
python
from collections import Counter
class Solution:
def findAnagrams(self, s: str, p: str):
n, k = len(s), len(p)
if n < k:
return []
need = Counter(p)
window = Counter()
res = []
left = 0
for right in range(n):
# 把右边新字符加进窗口
window[s[right]] += 1
# 保持窗口长度为 k
if right - left + 1 > k:
window[s[left]] -= 1
if window[s[left]] == 0:
del window[s[left]]
left += 1
# 长度刚好是 k 时,比较两边字母袋
if right - left + 1 == k and window == need:
res.append(left)
return res

有一个"魔法队伍",它只做两件事:
-
只保留可能成为最大值的同学
-
队伍里的数字从前到后是从大到小排好的
窗口往右移动时:
-
新同学要进来时:
-
把队尾比他小或等于他的同学全赶走(因为以后不可能当最大值)
-
然后自己站到队尾
-
-
如果队头同学已经"走出窗口范围"
→ 把他从队头移除
-
每次窗口形成一个完整长度
k后,→ 队头的那位就是当前窗口的最大值
核心算法思路(单调队列 + 滑动窗口)
-
用
deque存储的是 下标,不直接存值 -
队列中的下标,对应的
nums[index]是 从大到小 的 -
维护规则:
-
进新元素
i前,从队尾把所有nums[队尾] <= nums[i]的下标弹出 -
把
i放入队尾 -
如果队头下标 <
i - k + 1,说明已经不在当前窗口中,弹出队头 -
当
i >= k - 1时,开始记录答案:res.append(nums[deque[0]])
-
时间复杂度:O(n),每个元素最多入队一次出队一次。
python
from collections import deque
class Solution:
def maxSlidingWindow(self, nums, k: int):
q = deque() # 存下标,保持对应值单调递减
res = []
for i, x in enumerate(nums):
# 1. 保持队列单调递减:新来的比队尾大,就把队尾弹掉
while q and nums[q[-1]] <= x:
q.pop()
# 2. 把当前下标入队
q.append(i)
# 3. 如果队头已经滑出窗口左边,就弹掉
if q[0] <= i - k:
q.popleft()
# 4. 当 i >= k-1 时,窗口已经形成,可以记录最大值
if i >= k - 1:
res.append(nums[q[0]])
return res