LeetCode 链接
题目描述
给定两个字符串 s1 和 s2,判断 s2 中是否存在一个连续子串,其字符组成与 s1 完全相同(顺序无关)。例如,若 s1 = "ab",则合法的子串可以是 "ab"、"ba" 或其他任意排列组合。
示例 1:
输入:s1 = "ab",s2 = "eidbaooo"
输出:true
解释:s2 中存在子串 "ba",其字符组成与 s1 相同。
示例 2:
输入:s1 = "ab",s2 = "eidboaoo"
输出:false
解释:s2 中所有长度为 2 的子串均不满足字符组成要求。
核心思路
本题的关键在于高效判断 s2 中是否存在与 s1 字符组成相同的子串。由于直接暴力枚举所有可能的子串会导致时间复杂度过高(如 s2 长度为 1e4 时,子串数量可达百万级),因此需要借助滑动窗口和字符计数的技巧来优化。
解法一:暴力枚举(直观但低效)
思路:
- 统计
s1中各字符的出现次数。 - 遍历
s2的所有可能子串(长度等于s1),逐一比对字符计数是否一致。
代码实现:
class Solution {
public boolean checkInclusion(String s1, String s2) {
if (s1.length() > s2.length()) return false;
// 统计s1的字符频率
Map<Character, Integer> target = new HashMap<>();
for (char c : s1.toCharArray()) {
target.put(c, target.getOrDefault(c, 0) + 1);
}
// 枚举s2的所有长度为s1.length()的子串
for (int i = 0; i <= s2.length() - s1.length(); i++) {
Map<Character, Integer> window = new HashMap<>();
for (int j = i; j < i + s1.length(); j++) {
char c = s2.charAt(j);
window.put(c, window.getOrDefault(c, 0) + 1);
}
// 判断当前窗口是否与目标完全匹配
if (target.equals(window)) return true;
}
return false;
}
}
特点:
✅ 代码逻辑简单直观
❌ 时间复杂度为 O(m*n)(m 和 n 分别为 s1 和 s2 的长度),无法通过大数据量测试
解法二:滑动窗口 + 动态计数(最优解)
核心思想:
通过维护一个固定大小的窗口(长度等于 s1),逐步向右滑动窗口,实时更新窗口内的字符计数,并与 s1 的计数进行比对。当窗口计数完全匹配时,立即返回结果。
关键优化点:
- 滑动窗口:避免重复计算,每次只需处理新增和移出的字符。
- 数组代替哈希表:利用 ASCII 码的特性,用长度为 26 的数组存储字符计数,提升查询效率。
代码实现:
class Solution {
public boolean checkInclusion(String s1, String s2) {
if (s1.length() > s2.length()) return false;
int[] target = new int[26]; // s1的字符计数
int[] window = new int[26]; // 当前窗口的字符计数
// 初始化窗口和目标计数
for (int i = 0; i < s1.length(); i++) {
target[s1.charAt(i) - 'a']++;
window[s2.charAt(i) - 'a']++;
}
// 初步比对
if (Arrays.equals(target, window)) return true;
// 滑动窗口
for (int i = s1.length(); i < s2.length(); i++) {
// 添加新字符
char newChar = s2.charAt(i);
window[newChar - 'a']++;
// 移出旧字符
char oldChar = s2.charAt(i - s1.length());
window[oldChar - 'a']--;
// 判断是否匹配
if (Arrays.equals(target, window)) return true;
}
return false;
}
}
特点:
✅ 时间复杂度优化至 O(n)(仅需遍历一次 s2)
✅ 空间复杂度优化至 O(1)(固定大小数组)
❌ 需要处理字符计数的动态增减逻辑
代码对比与选择建议
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力枚举 | O(m*n) | O(m) | 数据量极小(如面试题) |
| 滑动窗口 | O(n) | O(1) | 实际工程场景 |
总结:滑动窗口方案通过巧妙的动态计数和窗口滑动策略,将时间复杂度从 O(m*n) 降低到线性级别,是本题的最优解。建议优先掌握该方法,并理解其核心思想——通过维护固定窗口减少重复计算。

被折叠的 条评论
为什么被折叠?



