文章目录
- [1. Problem 1 无重复字符的最长字串(# 3)](# 3))
-
- [1.1 题目表述](#1.1 题目表述)
- [1.2 题解](#1.2 题解)
- [2. Problem 2 找到字符串中所有字母异位词(# 438)](# 438))
-
- [2.1 题目表述](#2.1 题目表述)
- [2.2 题解](#2.2 题解)
- [3. 总结](#3. 总结)
1. Problem 1 无重复字符的最长字串(# 3)
1.1 题目表述
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串的长度。
示例 1:
plain
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。
示例 2:
plain
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
plain
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
-
0 <= s.length <= 5 * 10(4) -
s由英文字母、数字、符号和空格组成
1.2 题解
本题本质上是找出最长的无重复字符的子串,而针对是否重复问题,最快的搜索方法是哈希表,针对子串范围问题最优处理思路是双指针,因此本题适合使用滑动窗口法(哈希表/数组+双指针)进行处理。其伪代码如下:
plain
// 输入:字符串 s
// 输出:无重复字符的最长子串长度
// 1. 初始化
left = 0
max_len = 0
// 哈希表:key = 字符,value = 字符最后一次出现的索引
char_map = 空哈希表
// 2. 右指针遍历字符串
for right from 0 to len(s)-1:
current_char = s[right]
// 如果当前字符已在窗口中出现过,更新左指针
if current_char in char_map and char_map[current_char] >= left:
left = char_map[current_char] + 1
// 更新当前字符的最后出现位置
char_map[current_char] = right
// 计算当前窗口长度,更新最大长度
current_len = right - left + 1
if current_len > max_len:
max_len = current_len
// 3. 返回结果
return max_len
-
哈希表:记录当前已经出现的字符以及每个字符最近出现的位置
-
左指针:滑动窗口左边界,保证窗口中没有重复元素
-
右指针:滑动窗口右边界,遍历寻找更长无重复子串
-
指针之间关系:右指针始终向右移动,左指针根据右指针遍历信息若遇到重复字符则跟着向右移动,记录滑动窗口(两指针间)的长度,并寻找最大值。
2. Problem 2 找到字符串中所有字母异位词(# 438)
2.1 题目表述
给定两个字符串 s 和 p,找到 s中所有 p的 异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
示例 1:
plain
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
示例 2:
plain
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
提示:
-
1 <= s.length, p.length <= 3 * 10(4) -
s和p仅包含小写字母
2.2 题解
本题需要找 s 中 p 的所有异位词的位置,这要求:1)子串长度相同;2)符合异位词特征(字符出现频率或重排后完全一致),由于子串的长度要求是固定的,因此滑动窗口的大小也就是恒定的了,因此本题中指针的概念便也随之弱化。另外由于本题涉及异位词的相关问题,因此可以参考 字母异位词分组(# 49) 题的思路进行判断。伪代码可表示为:
plain
// 输入:字符串 s, p
// 输出:所有异位词子串的起始索引列表
len_s = length(s)
len_p = length(p)
结果列表 result = 空列表
// 边界情况:s比p短,直接返回空
if len_s < len_p:
return result
// 1. 初始化字符计数数组(26个小写字母)
count_p = [0] * 26
count_s = [0] * 26
// 2. 统计p的字符计数
for i from 0 to len_p-1:
count_p[ ord(p[i]) - ord('a') ] += 1
count_s[ ord(s[i]) - ord('a') ] += 1
// 3. 检查初始窗口是否匹配
if count_s == count_p:
add 0 to result
// 4. 滑动窗口遍历剩余部分
for left from 1 to len_s - len_p:
right = left + len_p - 1
// 移出左边界前一个字符
count_s[ ord(s[left-1]) - ord('a') ] -= 1
// 移入新的右边界字符
count_s[ ord(s[right]) - ord('a') ] += 1
// 检查当前窗口是否匹配
if count_s == count_p:
add left to result
// 5. 返回结果
return result
在这里,我们考虑了边界情况(p长度大于s长度),在实际应用中,进行边界情况的考虑检验测试十分重要。
3. 总结
滑动窗口的关键,不只是"维护一个区间",而是学会把原本静态的子串问题,转化为一个动态更新的过程。无重复字符的最长子串,本质是在不断扩展与收缩窗口中维持"区间合法";而寻找异位词,则是在固定窗口大小下持续比较字符分布。两道题看似形式不同,但都体现了滑动窗口对"连续区间问题"的高效处理能力:避免每次从头判断,而是在移动中完成更新与筛选。
更深一层看,滑动窗口体现的是一种过程化思维:问题的答案不是一次性算出来的,而是在窗口推进中逐步逼近的。它要求我们时刻思考窗口内哪些信息需要保留、哪些需要剔除,以及边界变化会带来什么影响。尤其在异位词问题中,对边界情况的重视也提醒我们,真正成熟的解题能力,不只在于掌握方法,更在于对细节、约束和有效性的持续把控。