踩坑记录[8]——LeetCode 567题:字符串的排列
题目描述
给你两个字符串 s1
和 s2
,写一个函数来判断 s2
是否包含 s1
的排列。如果是,返回 true
;否则,返回 false
。
换句话说,s1
的排列之一是 s2
的 子串 。
示例 1:
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
示例 2:
输入:s1= "ab" s2 = "eidboaoo"
输出:false
答案(C++语言,已通过LeetCode测试)
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char, int> need, window;
for (auto c: s1) need[c]++;
int left = 0, right = 0;
int value = 0;
while(right < s2.size()){
char c = s2[right];
right++;
if(need.count(c)){
window[c]++;
if (window[c] == need[c]) value++; // 相同时才++
}
while(value == need.size()){
// 判断是否满足条件
if ((right - left) == s1.size()){
cout << left << right << s1.size() << endl;
return true;
}
// 执行收缩窗口
char c = s2[left];
left++;
if(need.count(c)){
if (window[c] == need[c]) value--;
window[c]--;
}
}
}
return false;
}
};
方案描述
本题目主要利用滑动窗口的思想,在遍历过程中动态维护窗口,通过更新窗口内字符的出现次数来判断是否满足条件。具体步骤如下:
- 初始化映射表: 创建两个unordered_map,
need
用于存储字符串s1
中字符的出现次数,window
用于存储当前窗口内字符的出现次数。 - 滑动窗口遍历: 使用双指针
left
和right
遍历s2
字符串。 - 向右扩展窗口: 右指针
right
向右移动,每次移动将当前字符加入窗口window
,并更新该字符在窗口中的出现次数。 - 检查窗口内字符是否满足要求: 检查窗口内字符的出现次数是否满足要求。如果
window
中某个字符的出现次数等于need
中该字符的出现次数,则将value
值加一。 - 收缩窗口: 当窗口内的字符已经满足要求时,开始收缩窗口。左指针
left
向右移动,每次移动将当前字符从窗口中移出,并更新窗口内该字符的出现次数。如果某个字符的出现次数小于need
中该字符的出现次数,则将value
值减一。 - 判断是否找到满足条件的窗口: 在窗口大小等于
s1
的长度时,即找到了一个长度与s1
相等的子串。此时返回true
。 - 未找到满足条件的窗口: 如果遍历完整个
s2
字符串仍未找到满足条件的窗口,则返回false
。
踩坑记录
题目难度:中等
本题涉及到字符串的子串,一般需要考虑滑动窗口算法进行解决。滑动窗口算法本质上也是利用双指针,控制窗口的大小和移动。
-
找到满足的字符串的条件是
(right - left) == s1.size()
而不是
(right - left+1) == s1.size()
。 这是因为在前面的时候有一个right++,即本轮中right已经比当前使用的多了1,所以不用再+1了。