滑动窗口算法详解:如何求解字符串的最短子串问题
创作时间:
作者:
@小白创作中心
滑动窗口算法详解:如何求解字符串的最短子串问题
引用
CSDN
1.
https://blog.csdn.net/m0_70871140/article/details/144201034
在编程领域,字符串问题是一个经典的研究方向,既涉及算法优化,也考验编码实现能力。在众多问题中,寻找字符串的最短子串问题是一道具有实际意义的题目。它不仅在面试中常见,而且其解决方法在文本处理、数据分析等场景中应用广泛。本文将深入解析这一问题,并详细介绍如何利用滑动窗口算法实现高效解法。
一、问题描述
任务:给定一个仅由小写英文字母组成的字符串,找到其最短的子串,要求这个子串必须包含输入字符串中出现过的所有不同字符。
输入:
- 一个长度不超过 200 的字符串,仅由小写英文字母组成。
输出:
- 满足条件的最短子串。如果存在多个解,输出其中最靠左的子串。
示例:
- 输入:
adfasjdoiasdfa - 输出:
fasjdoi
二、问题分析
- 任务拆解:
- 首先,需要找到输入字符串中的所有不同字符。
- 然后,动态追踪某个子串是否包含了所有这些字符。
- 最后,输出满足条件的最短子串。
- 挑战点:
- 如何高效地统计当前子串中所有字符的频次。
- 在满足条件的情况下,如何动态缩短子串以找到最短解。
- 解法思路:
- 本问题需要快速调整子串范围,以避免暴力枚举所有子串带来的性能问题。
- 滑动窗口是一种高效的解决方案,它通过动态调整窗口边界,在一次遍历中完成任务。
三、解法:滑动窗口
滑动窗口算法是一种巧妙解决子串问题的方法,通过两个指针(left和right)表示窗口的起止位置,动态调整窗口大小,以满足特定条件。以下是详细的步骤:
步骤 1:初始化变量
- 用left和right两个指针表示窗口的左右边界,初始值均为0。
- 用一个Map(哈希表)存储当前窗口中每个字符的频次。
- 用minLen记录当前满足条件的最小子串长度,result保存最终结果。
步骤 2:右指针扩展窗口
- 每次移动右指针,将当前字符加入窗口。
- 更新哈希表,增加该字符在窗口中的频次。
步骤 3:左指针收缩窗口
- 当窗口中包含所有目标字符时,尝试移动左指针缩小窗口。
- 每次移动左指针时,检查当前窗口是否仍然满足条件:
- 如果满足,则更新结果为当前子串;
- 如果不满足,则停止收缩。
步骤 4:返回结果
- 当右指针遍历完字符串后,返回记录的最短子串。
四、代码实现
以下是基于上述思路的 Java 实现:
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class No19 {
public static String solution(String str) {
int left = 0, right = 0; // 初始化窗口边界
String result = ""; // 保存最短子串结果
Map<Character, Integer> map = new HashMap<>(); // 存储窗口内字符频次
// 初始化目标字符集合
for (char c : str.toCharArray()) {
map.put(c, 0);
}
int uniqueCharCount = map.size(); // 目标字符的总数
int minLen = Integer.MAX_VALUE; // 初始化最小长度为最大值
while (right < str.length()) {
char c = str.charAt(right);
map.put(c, map.getOrDefault(c, 0) + 1); // 更新当前字符频次
// 如果窗口包含所有目标字符,则尝试缩小窗口
while (map.values().stream().allMatch(count -> count > 0)) {
if (right - left + 1 < minLen) {
minLen = right - left + 1;
result = str.substring(left, right + 1); // 更新结果
}
// 收缩窗口:减少左边字符的频次
char leftChar = str.charAt(left);
map.put(leftChar, map.get(leftChar) - 1);
left++;
}
right++; // 扩展窗口
}
return result;
}
public static void main(String[] args) {
System.out.println(Objects.equals(solution("adfasjdoiasdfa"), "fasjdoi"));
}
}
五、代码解析
- 初始化目标字符:
for (char c : str.toCharArray()) {
map.put(c, 0);
}
将所有字符初始化为频次为0,这确保我们可以直接更新窗口中字符的频次,而无需反复判断字符是否已出现。
- 动态调整窗口
- 扩展窗口:
map.put(c, map.getOrDefault(c, 0) + 1);
通过右移指针,将当前字符加入窗口。
- 收缩窗口:
map.put(leftChar, map.get(leftChar) - 1);
left++;
判断窗口是否满足条件,满足时通过左移指针尝试缩小窗口。
- 更新结果
if (right - left + 1 < minLen) {
minLen = right - left + 1;
result = str.substring(left, right + 1);
}
记录满足条件的最短子串,同时确保是最靠左的解。
六、时间复杂度分析
- 初始化目标字符:需要遍历一次字符串,时间复杂度为 O(n)。
- 滑动窗口调整:每个指针最多遍历整个字符串一次,总体复杂度为 O(n)。
- 判断条件:map.values().stream().allMatch(...)的复杂度为 O(1),因为目标字符的数量是有限的(最多 26 个小写字母)。
总时间复杂度为 O(n)。
七、总结与拓展
滑动窗口是一种高效解决子串或子数组问题的算法,尤其适合处理动态窗口大小的问题。本题通过滑动窗口巧妙调整子串范围,达到了高效计算的目标。总结几点收获:
- 问题拆解能力
- 通过明确需求,合理分解任务,确保算法实现简单清晰。
- 数据结构选择
- 使用Map(哈希表)统计字符频次,提升了字符操作的效率。
- 滑动窗口优化
- 通过动态调整窗口边界,避免了暴力枚举带来的性能开销。
热门推荐
秦惠文王河西之战:奠定霸业的关键一役
嬴驷军事改革:从历史到游戏的体验
川大书法奠基人侯开嘉:以严谨治学开创书法教育新篇
从40亿科研投入到全球百强,川大实现跨越式发展
华西第四教学楼修缮完成,百年建筑重现风采
高中地理必备:中国省份简称与省会记忆指南
记住这串口诀,轻松掌握中国34省区简称与省会
智能水表计量的数据参数有哪些?
一升汽油等于多少公斤?三种标号密度数据及应用
《五百年桑田沧海》:一首激励人心的生命赞歌
《别时容易见时难》:47集短剧用BE结局诠释人生聚散
《五百年沧海桑田》:一首歌,一段传奇
呼吸科专家详解:秋冬咳嗽有痰的家庭护理与用药指南
掌握破十法:小学一年级数学必备减法技巧
嬴驷称王,魏国颤抖:张仪公孙衍联手出击
一年级数学破十法:20以内退位减法计算技巧
《甄嬛传》再聚首,两岸影视热持续升温
从《甄嬛传》看职场生存法则,你站哪边?
靛色与夏威夷蓝:打造绝美搭配指南
裕仁天皇:从军国主义到战后转型的关键角色
日本投降背后的国际秩序大变局
揭秘日本投降:原子弹与苏联宣战的双重压力
无条件投降背后的秘密:二战结束的历史真相
1oz等于多少ml?一文详解盎司与毫升换算
嬴驷:铁腕治国的雄主
秦惠文王的军事改革:古代管理智慧的现代启示
员工培训对企业的影响有哪些
外地车都纷纷赶来!杭州这口鲜,进入最佳赏味期!百年秘方后劲太大!
西双版纳机场推“候鸟无忧”服务,老年旅客量三年增34万
秦惠文王东进攻魏:奠定霸业的关键之战