题意
给两个字符串s和t,要求找到s中最小的一段,使:t中的全部字母在s的这一段中出现。
要求时间复杂度 O(n) 。
思路
双指针。
我们设置两个指针i和j指向s中满足条件的那一段的头和尾。
如果当前段满足条件,更新结果并且将i挪到下一个t中出现的字母的位置。如果不满足条件的话,将j后挪一个。
我们可以来看看这个模拟过程:
在判断是否满足条件时,有两种方法:
- 开一个
vis[]
和need[]
数组,其中need记录t中出现的字母的出现次数,vis记录s中我们扫过的那一段出现的字母的次数。每次去判断是否满足vis[i] >= need[i]
即可。只不过时间会增加常熟时间复杂度。 - 在上面开了
vis[]
数组和need[]
数组的基础上,我们再增加一个变量count
来记录我们s中我们指向的那一段满足条件字母达到了多少个。注意每次更新的判断基于vis和need。
时间复杂度
因为对于指针i和j,都只扫描字符串一遍,所以时间复杂度为 O(n)
细节
边界情况。开始时用j作为循环终止条件。并且最后还存在:j已经挪到了最后一位,但是i需要继续往后挪的情况。处理的很麻烦。正确的思路是:将i作为终止循环的条件,去规范j。
代码
algorithm 1
class Solution {
public:
bool check(int *vis, int *need) {
for (int i = 'A'; i <= 'z'; i++) {
if (vis[i] < need[i]) return false;
}
return true;
}
string minWindow(string s, string t) {
int need[200] = {0};
int vis[200] = {0};
for (auto ch : t) need[ch]++;
int i = 0, j = -1, res = INT_MAX, st = -1, ed = -1;
while (i <= j || j == -1) {
if (check(vis, need)) {
if (j - i + 1 <= res) {
res = j - i + 1;
st = i, ed = j;
}
if (need[s[i]]) vis[s[i++]]--;
while (i <= j && !need[s[i]]) i++;
} else if (j == s.length()) {
break;
} else {
++j;
if (j < s.length() && need[s[j]]) vis[s[j]]++;
}
}
return st >= 0 && ed >= 0 ? s.substr(st, ed - st + 1) : "";
}
};
algorithm 2
class Solution {
public:
string minWindow(string s, string t) {
int need[200] = {0};
int vis[200] = {0};
for (auto ch : t) need[ch]++;
int i = 0, j = -1, res = INT_MAX, st = -1, ed = -1, count = 0;
while (i <= j || j == -1) {
if (count == t.size()) {
if (j - i + 1 <= res) {
res = j - i + 1;
st = i, ed = j;
}
if (need[s[i]]) {
if (vis[s[i]] <= need[s[i]]) count--;
vis[s[i]]--;
i++;
}
while (i <= j && !need[s[i]]) i++;
} else if (j == s.length()) {
break;
} else {
++j;
if (j < s.length() && need[s[j]]) {
vis[s[j]]++;
if (vis[s[j]] <= need[s[j]]) count++;
}
}
}
return st >= 0 && ed >= 0 ? s.substr(st, ed - st + 1) : "";
}
};