有一说一今天的题有点难。
1、给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度。示例:
输入: s = "abcabcbb" 输出: 3
这个题让人想到了双指针,但由于是字符串,所需要通过一定工具来记录双指针之间的字符,进而可以想到用哈希表的方式来完成。如果表中(也就是窗口中有重复,则先记录子串长度再逐渐删除重复元素以及之前的元素)。因此可以有以下代码:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set<char>u;
int left=0,right=0,Max=0;
int n=s.size();
while(right<n){
if(u.end()==u.find(s[right])){
u.insert(s[right++]); //没有重复元素就插入hash表
Max=max(right-left,Max); //寻找最长子串数
} else u.erase(s[left++]); //左边窗口滑动到没有重复元素为止
}
return Max; //等到右窗口到达边界就可以得到最大子串数量
}
};
在时间复杂度上大概为O(n),空间复杂度由于有哈希表,所以是O(n),实质上应该和字符串内部的元素数量一致。
2、给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。换句话说,s1 的排列之一是 s2 的 子串 。
示例:输入:s1 = "ab" s2 = "eidbaooo" 输出:true
输入:s1= "ab" s2 = "eidboaoo"输出:false
这个感觉同样可以用滑动窗口来解决,代码如下:
(1)滑动窗口法
class Solution {
public:
bool checkInclusion(string s1, string s2) {
int n = s1.length(), m = s2.length(); //先检测下两者的长度
if (n > m) {
return false;
}
vector<int> cnt1(26), cnt2(26);
for (int i = 0; i < n; ++i) { //设置一个定长的滑动窗口
++cnt1[s1[i] - 'a'];
++cnt2[s2[i] - 'a'];
}
if (cnt1 == cnt2) { //如果相等就直接返回true
return true;
}
for (int i = n; i < m; ++i) { //cnt2的窗口逐渐滑动,直到相等返回true或者不相等返回false
++cnt2[s2[i] - 'a'];
--cnt2[s2[i - n] - 'a'];
if (cnt1 == cnt2) {
return true;
}
}
return false;
}
};
针对这个问题还有人给出了优化解法:
class Solution {
public:
bool checkInclusion(string s1, string s2) {
int n = s1.length(), m = s2.length();
if (n > m) { //先查看是否满足s1成为s2子数组的情况
return false;
}
vector<int> cnt(26);
for (int i = 0; i < n; ++i) { //在cnt中不断加入s1元素,减去s2元素,留下的就是两个数组在前n个元素中不一样的元素。
--cnt[s1[i] - 'a'];
++cnt[s2[i] - 'a'];
}
int diff = 0;
for (int c: cnt) { //利用diff统计在前n个元素中不一样的元素
if (c != 0) {
++diff;
}
}
if (diff == 0) { //这里说明s1和s2前面n个元素恰好一样
return true;
}
for (int i = n; i < m; ++i) { //这里就要陆续在cnt中添加+减少元素
int x = s2[i] - 'a', y = s2[i - n] - 'a'; //窗口左右端的边界
if (x == y) { //如果x和y一样的,对于diff没有影响
continue;
}
if (cnt[x] == 0) { //被剔除的这一个元素是s1和s2共有(因为不存在于cnt),diff+1
++diff;
}
++cnt[x]; //推进一步x
if (cnt[x] == 0) { //推进之后的元素x不存在于cnt,所以是s1和s2共有,diff-1
--diff;
}
if (cnt[y] == 0) { //和x同理
++diff;
}
--cnt[y];
if (cnt[y] == 0) {
--diff;
}
if (diff == 0) { //再次检验diff是否为0
return true;
}
}
return false;
}
};
这一个优化的方法核心在于ent中的元素,通过判断s1与s2的相同/不同元素来实现。
时间复杂度:O(n+m+∣Σ∣),
空间复杂度:O(∣Σ∣)。
∣Σ∣代表字符集,也就是26个字母。
(2)双指针
class Solution {
public:
bool checkInclusion(string s1, string s2) {
int n = s1.length(), m = s2.length();
if (n > m) { //排除s1比s2大的情况
return false;
}
vector<int> cnt(26);
for (int i = 0; i < n; ++i) { //减去cnt中的s1包含元素的权重
--cnt[s1[i] - 'a'];
}
int left = 0;
for (int right = 0; right < m; ++right) { //保证左右指针不溢出
int x = s2[right] - 'a';
++cnt[x]; //加入s2中右指针元素所在权重
while (cnt[x] > 0) { //如果权重大于0说明不在s1中
--cnt[s2[left] - 'a']; //减去左边框字母的权重(左边框移动)
++left;
}
if (right - left + 1 == n) { //如果有左右指针相隔n,说明存在最大子串
return true;
}
}
return false;
}
};
时间复杂度:O(n+m+∣Σ∣)。
空间复杂度:O(∣Σ∣)。