LeetCode 395题解析:分治法求解最长K重复字符子串
1. 问题背景与理解LeetCode 395题至少有 K 个重复字符的最长子串是一个经典的字符串处理问题考察我们对分治算法和递归思想的理解。题目要求我们找到给定字符串中最长的子串其中每个字符的出现次数都不少于k次。我第一次遇到这个问题时直觉上觉得可以用滑动窗口解决但实际尝试后发现常规的滑动窗口方法会遇到困难。这促使我深入思考更合适的解法。经过几次失败的尝试后我意识到这个问题需要结合分治思想和递归来解决。2. 问题分析与解法思路2.1 问题重述给定一个字符串s和一个整数k我们需要找到s的最长子串的长度使得该子串中的每个字符的出现次数都不少于k。例如输入s aaabb, k 3输出3解释最长子串为aaa其中a出现3次。2.2 核心难点解析这个问题的难点在于如何高效地处理字符串中不符合条件的字符。直接暴力解法的时间复杂度会很高无法通过LeetCode的测试用例。关键观察点如果一个字符在整个字符串中的总出现次数小于k那么它不可能出现在任何符合条件的子串中这样的字符实际上可以作为分隔符将原字符串分割成多个子问题我们可以递归地处理这些子问题最终合并结果2.3 分治算法设计基于以上观察我们可以设计如下分治算法统计字符串中每个字符的出现频率找到第一个出现频率小于k的字符分割点以该字符为分割点将字符串分成左右两部分递归地在左右子字符串中寻找符合条件的子串返回左右结果中的较大值如果字符串中所有字符的出现频率都≥k则整个字符串就是符合条件的子串直接返回其长度。3. 代码实现与详细解析3.1 基础实现def longestSubstring(s: str, k: int) - int: if len(s) k: return 0 # 统计字符频率 freq {} for c in s: freq[c] freq.get(c, 0) 1 # 找到第一个不符合条件的字符作为分割点 split None for i, c in enumerate(s): if freq[c] k: split c break # 如果没有分割点说明整个字符串都符合条件 if not split: return len(s) # 分割字符串并递归处理 max_len 0 for substr in s.split(split): max_len max(max_len, longestSubstring(substr, k)) return max_len3.2 优化实现上面的基础实现可以进一步优化减少不必要的字符串分割操作def longestSubstring(s: str, k: int) - int: if len(s) k: return 0 freq {} for c in s: freq[c] freq.get(c, 0) 1 # 收集所有不符合条件的字符 splits [c for c in freq if freq[c] k] if not splits: return len(s) # 使用正则表达式一次性分割所有不符合条件的字符 import re pattern |.join(re.escape(c) for c in splits) substrings re.split(pattern, s) max_len 0 for substr in substrings: max_len max(max_len, longestSubstring(substr, k)) return max_len3.3 复杂度分析时间复杂度O(n^2) 最坏情况下如字符串中字符都唯一且k1每次递归处理n-1长度的字符串 空间复杂度O(n) 递归栈的深度4. 算法正确性证明为了验证我们算法的正确性可以考虑以下几点基本情况当字符串长度小于k时显然没有符合条件的子串递归情况当存在出现次数小于k的字符时任何符合条件的子串都不能包含该字符因此可以安全地分割合并情况最终结果是左右子问题结果的较大值这保证了我们找到的是全局最优解终止条件当字符串中所有字符出现次数都≥k时整个字符串就是解5. 边界条件与测试用例5.1 常见边界情况空字符串或k0字符串中所有字符都相同字符串中所有字符都唯一k1的特殊情况非常长的字符串测试算法效率5.2 测试用例示例test_cases [ (aaabb, 3, 3), (ababbc, 2, 5), (abcde, 2, 0), (, 1, 0), (a*10000, 1, 10000), (abcdefghijklmnopqrstuvwxyz, 1, 26), (aabbbcccdddeee, 3, 9) ] for s, k, expected in test_cases: assert longestSubstring(s, k) expected6. 性能优化与替代解法6.1 滑动窗口法的局限性很多同学会首先尝试滑动窗口法但发现难以处理窗口内字符频率的统计。主要困难在于不知道窗口应该包含哪些字符无法确定何时扩展/收缩窗口难以同时满足所有字符频率≥k的条件6.2 改进的滑动窗口法实际上这个问题可以使用滑动窗口法解决但需要一些技巧限定窗口中不同字符的数量t从1到26遍历对于每个t使用滑动窗口统计恰好包含t种字符的子串检查这些子串是否满足所有字符频率≥k记录满足条件的最大子串长度这种方法的时间复杂度是O(26n)O(n)比递归解法更高效。6.3 滑动窗口实现def longestSubstring(s: str, k: int) - int: max_len 0 for t in range(1, 27): # 尝试1到26种不同字符 left 0 freq [0] * 26 unique total 0 # unique:当前窗口不同字符数, total:满足≥k的字符数 for right in range(len(s)): idx ord(s[right]) - ord(a) if freq[idx] 0: unique 1 freq[idx] 1 if freq[idx] k: total 1 while unique t: left_idx ord(s[left]) - ord(a) if freq[left_idx] k: total - 1 freq[left_idx] - 1 if freq[left_idx] 0: unique - 1 left 1 if unique total t: max_len max(max_len, right - left 1) return max_len7. 实际应用与扩展7.1 实际应用场景这类字符串处理问题在实际中有多种应用基因序列分析寻找满足特定重复模式的DNA片段文本挖掘提取高频词汇组成的连续文本段数据清洗识别并处理异常或低频字符序列7.2 问题变种至少包含m个不同字符且每个字符至少出现k次的最长子串最多包含m个不同字符且每个字符至少出现k次的最长子串允许最多x个字符不满足条件的最长子串7.3 学习建议先理解分治法的基本思想再应用到字符串问题上从简单例子入手手动模拟算法执行过程比较不同解法的时间/空间复杂度尝试自己设计测试用例包括边界情况8. 常见错误与调试技巧8.1 常见错误递归终止条件不正确导致无限递归字符串分割时处理不当丢失部分子串频率统计错误特别是Unicode字符的处理没有正确处理k0或空字符串的情况8.2 调试技巧添加打印语句跟踪递归过程和变量状态对小规模测试用例手动计算预期结果使用LeetCode的测试用例失败信息定位问题比较递归和滑动窗口两种解法的结果是否一致8.3 性能调优对于Python避免频繁的字符串拼接和分割使用更高效的数据结构统计字符频率对于滑动窗口法提前终止不必要的循环考虑使用记忆化技术优化递归解法9. 算法选择建议根据不同的场景可以选择不同的解法对于一般面试或编程竞赛分治递归解法更容易理解和实现对于性能要求高的场景滑动窗口解法更优对于特别长的字符串可能需要考虑并行处理或更高级的算法在实际应用中还需要考虑字符串的预期长度字符集的大小是否仅限于小写字母k值的典型范围是否需要多次查询不同的k值10. 个人实践心得在解决这个问题时我总结了以下几点经验不要被表面现象迷惑看似滑动窗口问题实则可能需要分治从简单例子入手手动计算小例子能帮助理解问题本质考虑极端情况空串、k0、所有字符相同等情况容易忽略多种解法对比理解不同解法的时间和空间复杂度差异测试驱动开发先写测试用例再实现确保覆盖各种情况这个问题很好地展示了算法思维的重要性。有时候看似复杂的问题可以通过巧妙的分解转化为更简单的子问题。这种分治思想不仅适用于字符串处理在树形结构、排序算法等领域也有广泛应用。

相关新闻

K次重复字符最长子串:分治与滑动窗口解法详解

K次重复字符最长子串:分治与滑动窗口解法详解

1. 问题理解与初步分析遇到"至少有 K 个重复字符的最长子串"这个问题时,我第一反应是这属于字符串处理中的滑动窗口类问题。但仔细分析后发现问题比常规滑动窗口更复杂,因为除了窗口边界移动外,还需要考虑字符出现频率的统计。题目…

2026/10/14 14:18:29 阅读更多 →
Linux磁盘清理与分区调整:从排查到自动化实践全指南

Linux磁盘清理与分区调整:从排查到自动化实践全指南

磁盘满了,这四个字大概是Linux运维生涯里出现频率最高的告警,没有之一。不管你是个人桌面用户、自建NAS的爱好者,还是正经管几台服务器的运维,几乎都经历过那种“明明没装几个东西,df -h一看某个分区已经100%”的窒息时…

2026/10/14 14:18:29 阅读更多 →
Maya材质种类详解:从Lambert到Arnold的表面性格与选择逻辑

Maya材质种类详解:从Lambert到Arnold的表面性格与选择逻辑

把“材质种类”这四个字写进笔记的第三年,我才承认自己一直在背一本用不起来的字典。Lambert、Blinn、Phong、Phong E、Anisotropic,单独拎出来每个都眼熟,一放进材质编辑器就全变天书。后来我把同一个球翻来覆去换材质,又关掉所有…

2026/10/14 14:18:29 阅读更多 →

最新新闻

手写负载均衡:从轮询到一致性哈希的算法与工程实践

手写负载均衡:从轮询到一致性哈希的算法与工程实践

手写负载均衡这件事,听起来像是业务代码写多了自娱自乐,但真把它当回事做一遍的人,对分布式流量的理解完全不一样。很多时候我们一直在用现成组件——Nginx、LVS、各种注册中心自带的负载策略——出了问题只能对着文档调参数,根本…

2026/10/14 19:56:03 阅读更多 →
GitLab初次安装避坑指南:Omnibus部署全流程与7类高频故障排查

GitLab初次安装避坑指南:Omnibus部署全流程与7类高频故障排查

1. 项目概述:为什么“初次安装”这件事值得单独写一篇长文?GitLab 不是点开浏览器输个网址就能用的 SaaS 服务——它是个需要你亲手部署、配置、调优的完整软件栈。我见过太多人卡在“初次安装”这一步:有人在 Ubuntu 上跑通了却在 CentOS 里…

2026/10/14 19:56:03 阅读更多 →
Python分支结构深度梳理:从if语法到match-case实践

Python分支结构深度梳理:从if语法到match-case实践

分支结构这件事,看起来很简单,不就是个 if 吗?但我在带新手和看项目代码时发现,很多人写了两三年 Python,对分支的理解还停留在"会用 if-else",一旦遇到复杂的判断逻辑就写得又臭又长&#xff0c…

2026/10/14 19:56:03 阅读更多 →
厂商内核与驱动定制全流程:从源码获取到调试避坑实战指南

厂商内核与驱动定制全流程:从源码获取到调试避坑实战指南

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

2026/10/14 19:56:03 阅读更多 →
Java桌面宠物实现:透明窗口与Swing动画机制详解

Java桌面宠物实现:透明窗口与Swing动画机制详解

简介:一份使用Java语言编写的桌面宠物小例子,面向初学图形界面编程的开发者,用于掌握窗口构建、事件监听、动画刷新与多线程协作等核心技能。资源包共80个文件,包含Java源码、编译后的class文件、宠物图片素材以及Eclipse工程配置…

2026/10/14 19:56:03 阅读更多 →
FPGA交通灯实战:EP2C35F672C6引脚约束与Quartus II 9.1工程落地

FPGA交通灯实战:EP2C35F672C6引脚约束与Quartus II 9.1工程落地

简介:本资源是一份基于Altera Cyclone II EP2C35F672C6 FPGA芯片实现的十字路口交通灯控制系统完整工程包,面向数字电路、EDA与FPGA初学者及课程设计学生,解决状态机建模、数码管动态扫描、多模块时序协同等典型实践难点。压缩包共120个文件&…

2026/10/14 19:55:02 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式: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/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/14 7:53:34 阅读更多 →