1. 问题背景与核心挑战
这道力扣经典难题(LeetCode 30)要求在一个字符串s中找出所有能按任意顺序串联给定单词数组words中所有单词的子串起始位置。看似简单的需求背后隐藏着三个关键难点:
- 排列组合爆炸:单词可以任意顺序排列,传统暴力解法需要对所有排列进行检查,时间复杂度高达O(N!),完全不可行
- 重叠匹配干扰:当单词长度相同时(如题目给定的固定长度),多个可能的子串可能重叠,需要精确处理边界
- 哈希冲突风险:使用哈希表优化时需要设计合理的哈希策略,避免不同单词组合产生相同哈希值
我最初尝试用朴素的滑动窗口解法时,遇到了窗口移动步长设置不当导致的漏检问题。后来发现必须结合单词长度来设计窗口滑动机制,这是许多初学者容易忽略的关键点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 滑动窗口与哈希表的协同设计
2.1 双哈希表的核心思想
解决方案采用主副两个哈希表的精妙设计:
- 主哈希表(target):预先存储
words中每个单词及其出现次数 - 副哈希表(current):动态记录当前窗口内匹配到的单词及其数量
python复制from collections import defaultdict
def findSubstring(s: str, words: List[str]) -> List[int]:
if not words or not s:
return []
word_len = len(words[0])
total_len = word_len * len(words)
target = defaultdict(int)
for word in words:
target[word] += 1
res = []
# 滑动窗口主体逻辑将在这里实现
return res
2.2 窗口滑动步长的数学原理
窗口移动不是简单的逐字符滑动,而是基于单词长度L的跳跃式移动。这是因为:
- 有效子串长度必须是
len(words) * L的整数倍 - 每次检查窗口后,下一次检查应该从
当前起始位置 + L开始
这使算法复杂度从O(n^2)降
