leetcode 567. 字符串的排列
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)
降低到线性级别,是本题的最优解。建议优先掌握该方法,并理解其核心思想——通过维护固定窗口减少重复计算。