-
2.25
- 依旧创建字典,
- 右指针移动时,判断当前字符是否>0,如果大于 0,则说明是关键字符,计数器+1, 无论如何当前字符字典内 -1
- 如果当计数器是等于目标字符串长度,就开始左指针移动,如果当前字符等于 0即为关键字符,少一个都不行,就 count—,无论如何当前字符字典内+1
- 用 start 和 minL,来截取结果字符串
-
2.6
- 两个阶段
a. 先移动右边界,如果需要,计数器+1。不管怎样,字典-1。右指针右移
b.如果窗口到位了,移动左指针,左边的数是需要的话。不管怎样字典+1。左指针右移
- 记得更新 minL 值和保存 start 以返回截取子串的位置
- 两个阶段
a. 先移动右边界,如果需要,计数器+1。不管怎样,字典-1。右指针右移
b.如果窗口到位了,移动左指针,左边的数是需要的话。不管怎样字典+1。左指针右移
-
1.23
- 注意 count,start 和 minLen 参数
核心思路:“伸缩法”
我们可以把滑动窗口想象成一个 “手风琴” 或者 “毛毛虫”:
- 向右扩张 (Right):如果不满足条件(还没包含
t中所有字符),right指针一直往右跑,吃进更多的字符,直到满足条件为止。 - 向左收缩 (Left):一旦满足条件了(当前窗口包含了
t的所有字符),left指针就开始往右缩,试图剔除多余的字符,寻找最小的长度。直到窗口不再满足条件,然后重新回到第 1 步。
class Solution {
public String minWindow(String s, String t) {
if (s == null || s.length() == 0 || t == null || t.length() == 0) {
return "";
}
// 1. 欠账表:记录我们需要什么字符,以及需要多少个
// ASCII 码只有 128 个,用数组比 HashMap 快
int[] need = new int[128];
for (char c : t.toCharArray()) {
need[c]++;
}
// 2. 定义滑动窗口的边界和核心变量
int left = 0, right = 0;
int count = 0; // 记录当前窗口里已经凑齐了 t 中的多少个字符
// 记录最小覆盖子串的起始位置和长度
int minLen = Integer.MAX_VALUE;
int start = 0;
// 3. 开始滑动
while (right < s.length()) {
char c = s.charAt(right); // 即将移入窗口的字符
// 如果这个字符是 t 需要的(need[c] > 0),那我们的“有效计数”就加 1
if (need[c] > 0) {
count++;
}
// 无论是不是需要的,账单都要减 1
// 如果是需要的,从 1 变 0;如果是多余的,从 0 变 -1
need[c]--;
// 窗口右移
right++;
// 4. 当 count == t.length() 时,说明当前窗口已经包含了 t 的所有字符
// 这时候我们需要通过移动 left 来缩小窗口,找最小值
while (count == t.length()) {
// 更新最小子串的记录
if (right - left < minLen) {
minLen = right - left;
start = left;
}
// 准备移出窗口的字符
char d = s.charAt(left);
// 恢复账单:移出去了,如果它本来是需要的字符,那账单得加回来
// 注意:只有当 need[d] == 0 时,说明它是关键字符(非富余字符)
// 移出它会导致窗口不合格,所以 count--
if (need[d] == 0) {
count--;
}
// 账单加回来(负数变 0,0 变正数)
need[d]++;
// 窗口左移
left++;
}
}
// 如果 minLen 没变过,说明没找到
return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
}
}