news 2026/9/23 2:50:30

LeetCode 30题解析:滑动窗口与哈希计数优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 30题解析:滑动窗口与哈希计数优化

1. 问题背景与核心挑战

LeetCode第30题"串联所有单词的子串"是字符串处理类题目中的经典难题。给定一个字符串s和一个字符串数组words,要求找出s中所有恰好由words中所有单词串联形成的子串的起始索引。words中的单词长度相同且可能重复。

这个问题的难点在于:

  1. 需要同时满足所有单词的完整出现(包括重复单词)
  2. 单词可以以任意顺序排列
  3. 子串必须严格连续且不包含其他字符
  4. 输入规模可能很大(s长度可达10^4,words长度可达5000)

2. 解题思路分析

2.1 暴力解法及其缺陷

最直观的解法是:

  1. 生成words所有可能的排列组合
  2. 在s中搜索每个组合的出现位置

但这种方法时间复杂度为O(N!×M),其中N是words长度,M是s长度。当N=10时,10! = 3628800,完全不可行。

2.2 滑动窗口+哈希计数的优势

更优的解法结合了滑动窗口和哈希计数:

  1. 利用所有单词长度相同的特点(设为word_len)
  2. 将问题转化为在s中寻找长度为word_len×words_num的子串
  3. 使用哈希表记录words中每个单词的出现次数
  4. 滑动窗口检查每个候选子串是否符合要求

这种方法将时间复杂度降为O(N×M),空间复杂度O(N)。

3. 详细实现步骤

3.1 预处理阶段

from collections import defaultdict def findSubstring(s, words): if not s or not words: return [] word_len = len(words[0]) total_len = word_len * len(words) word_count = defaultdict(int) for word in words: word_count[word] += 1

关键点:

  • 先处理边界情况(空输入)
  • 计算单个单词长度和总子串长度
  • 使用defaultdict统计每个单词出现次数

3.2 滑动窗口实现

result = [] n = len(s) for i in range(word_len): left = i curr_count = defaultdict(int) count = 0 for j in range(i, n - word_len + 1, word_len): word = s[j:j+word_len] if word in word_count: curr_count[word] += 1 count += 1 while curr_count[word] > word_count[word]: left_word = s[left:left+word_len] curr_count[left_word] -= 1 left += word_len count -= 1 if count == len(words): result.append(left) left_word = s[left:left+word_len] curr_count[left_word] -= 1 left += word_len count -= 1 else: curr_count.clear() count = 0 left = j + word_len return result

代码解析:

  1. 外层循环处理不同起始位置(0到word_len-1)
  2. 维护窗口[left, j]和当前计数curr_count
  3. 当发现不在words中的单词时,重置窗口
  4. 当某个单词超额时,移动左边界直到合规
  5. 当count等于words长度时,记录有效索引

4. 关键优化技巧

4.1 窗口移动的步长优化

由于所有单词长度相同,窗口可以以word_len为步长移动,而不是逐字符移动。这使得时间复杂度从O(M×N)降为O(M×word_len)。

4.2 哈希表的快速比对

使用两个哈希表:

  • word_count:记录words的标准分布
  • curr_count:记录当前窗口的实际分布

通过比较两个哈希表是否相同来判断窗口有效性,比字符串拼接比较效率高得多。

4.3 提前终止条件

当剩余字符串长度不足total_len时,可以提前终止搜索,避免无意义的检查。

5. 边界情况处理

5.1 输入为空的情况

if not s or not words: return []

5.2 单词长度不一致

题目已保证words中所有单词长度相同,但实际工程中需要验证:

if any(len(w) != word_len for w in words): return []

5.3 超长输入处理

对于特别长的s和words,可以考虑:

  1. 先检查s长度是否足够
  2. 使用更高效的数据结构(如原生dict代替defaultdict)

6. 复杂度分析

时间复杂度:O(word_len × n)。外层循环word_len次,内层最多处理n/word_len次。

空间复杂度:O(m)。需要存储words的哈希表,m为words中不同单词的数量。

7. 实际测试案例

测试用例1:

s = "barfoothefoobarman" words = ["foo","bar"] # 输出:[0,9]

测试用例2:

s = "wordgoodgoodgoodbestword" words = ["word","good","best","word"] # 输出:[]

测试用例3:

s = "a" * 10000 words = ["a"] * 5000 # 需要高效处理大规模输入

8. 常见错误与调试技巧

8.1 窗口边界错误

典型错误:没有正确处理窗口左边界移动,导致遗漏或重复计数。

调试方法:

  • 打印窗口变化时的left, j和curr_count
  • 使用小测试用例逐步跟踪

8.2 哈希表比对错误

常见问题:直接比较两个defaultdict对象可能不如预期。

解决方案:

  • 转换为普通dict再比较
  • 或者逐个键值比较

8.3 性能优化技巧

当words有很多重复单词时:

  1. 可以先统计unique单词
  2. 对s预处理建立单词位置索引
  3. 使用位图等压缩存储方式

9. 算法扩展思考

9.1 变体问题:单词长度不同

如果words中单词长度不同,问题会更复杂。可能的解法:

  1. 使用回溯法尝试所有组合
  2. 结合Trie树进行模式匹配

9.2 实际应用场景

这种算法可用于:

  1. DNA序列模式查找
  2. 文档内容指纹识别
  3. 网络流量特征检测

10. 个人实现心得

在实际编码中发现几个关键点:

  1. 窗口移动时要同时更新计数和窗口大小
  2. Python的defaultdict在清空时最好重新初始化,避免残留数据
  3. 对于超长重复字符串,可以先检查首字符是否匹配再进行完整比较
  4. 在竞赛中,可以预先计算所有可能的单词哈希值加速比较

一个容易忽略的优化:当words中存在重复单词时,可以先对words排序,然后在滑动窗口中对截取的单词也排序后比较,虽然增加了排序开销,但减少了哈希表操作。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 2:50:19

Android Loader异步加载器解析:TaoToken统一Key接入与配置验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/23 2:50:20

定向耦合器速查手册:3步搞定微波仿真配置痛点

定向耦合器速查手册:3步搞定微波仿真配置痛点 配置微波仿真环境时,是不是经常卡在参数设置上半天?S参数提取不对、隔离度计算报错、端口阻抗匹配失败,这些坑我全踩过。这份 定向耦合器 速查手册,基于十年射频工程实战,帮你避开80%的配置陷阱。Stack…

作者头像 李华
网站建设 2026/9/23 2:50:13

戴尔5557搞定版本升级API崩溃:5道高频面试题直击痛点

戴尔5557搞定版本升级API崩溃:5道高频面试题直击痛点 版本升级后 API 全变了,代码跑不通,线上服务报警,这种绝望感每个开发者都懂。更糟的是,面试官拿着这套旧逻辑问“为什么”,你卡壳,直接挂。 这就是 戴尔5557…

作者头像 李华
网站建设 2026/9/23 2:50:11

从决策树到随机森林:电信客户流失建模与参数调优实战

上个季度我在做一个电信客户留存分析,客户成功团队催着要一份流失预警名单。数据集是经典的电信客户流失数据,七千多条样本,二十来个字段。我当时顺手跑了三行默认参数的随机森林,AUC卡在0.75上不去。问题出在哪我很清楚&#xff…

作者头像 李华
网站建设 2026/9/23 2:49:14

脊椎锻炼源码深度剖析:3行代码搞定版本兼容与性能优化

脊椎锻炼源码深度剖析:3行代码搞定版本兼容与性能优化 上周刚把项目从 Python 3.8 升级到 3.11,结果 os.path 相关的 API 全变了,原本跑得飞起的脚本直接报错。更坑的是,为了兼容旧接口,我加了一堆 try-except ,结果 CPU 占用率飙升, 性能优化 全白做。…

作者头像 李华
网站建设 2026/9/23 2:49:02

颈椎保健操开发避坑指南:3个方案对比找出最佳实践

颈椎保健操开发避坑指南:3个方案对比找出最佳实践 官方文档堆成山,新手一眼就懵,抓不住重点直接劝退。想要搞懂【颈椎保健操】相关的逻辑实现,别再死磕那些晦涩的说明,直接看【最佳实践】才是正解。…

作者头像 李华