题目描述

滑动窗口
枚举最长子串中的字符种类数目 k i n d ∈ [ 1 , 26 ] kind \in [1,26] kind∈[1,26],对于给定的字符种类数量 k i n d kind kind,维护滑动窗口的左右边界 l e f t , r i g h t left,right left,right、滑动窗口内部每个字符出现的次数 w i n d o w window window、滑动窗口内的字符种类数目 t o t a l total total 以及滑动窗口内满足「出现次数不少于 k」条件的字符种类数目 s a t _ t o t a l sat\_total sat_total,在滑动窗口的维护过程中,得到最长子串的大小。
「当确定了窗口内所包含的字符数量时,区间重新具有了二段性质」。这是本题的滑动窗口解法和迄今为止做的滑动窗口题目的最大不同,本题需要手动增加限制,即限制窗口内字符种类。
【补充】「为什么需要先枚举 26 种可能性」:
首先我们知道「答案子串的左边界左侧的字符以及右边界右侧的字符一定不会出现在子串中,否则就不会是最优解」。
但如果我们只从该性质出发的话,朴素解法应该是使用一个滑动窗口,不断的调整滑动窗口的左右边界,使其满足「左边界左侧的字符以及右边界右侧的字符一定不会出现在窗口中」,这实际上就是双指针解法,但是如果不先敲定(枚举)出答案所包含的字符数量的话,这里的双指针是不具有单调性的。
换句话说,只利用这一性质是没法完成逻辑的。
这时候我们面临的问题是:性质是正确的,但是还无法直接利用。
因此我们需要先利用字符数量有限性(可枚举)作为切入点,使得「答案子串的左边界左侧的字符以及右边界右侧的字符一定不会出现在子串中」这一性质在双指针的实现下具有单调性。也就是「让区间重新具有二段性质」。
然后遍历 26 种可能性(答案所包含的字符种类数量),对每种可能性应用滑动窗口(由上述性质确保正确),可以得到每种可能性的最大值(局部最优),由所有可能性的最大值可以得出答案(全局最优)。
class Solution {
public int longestSubstring(String s, int k) {
int n = s.length();
int ans = 0;
for (int kind = 1; kind <= 26; kind++) {
// 滑动窗口左右边界
int left = 0;
int right = 0;
// 窗口内部每个字符出现的次数
int[] window = new int[26];
// 窗口内字符种类
int total = 0;
// 满足「出现次数不少于 k」条件的,窗口内字符种类
int sat_total = 0;
while (right < n) {
int cur = s.charAt(right) - 'a';
// 右边界入窗
window[cur]++;
if (window[cur] == 1) {
// 当前字符首次入窗
total++;
}
if (window[cur] == k) {
// 当前字符在窗口内出现次数满足k个
sat_total++;
}
// 当窗口内字符种类数超过限制时,左边界收缩
while (total > kind) {
int leftChar = s.charAt(left) - 'a';
if (window[leftChar] == 1) {
total--;
}
if (window[leftChar] == k) {
sat_total--;
}
window[leftChar]--;
left++;
}
// 当所有字符都符合要求,更新答案
if (sat_total == total) {
ans = Math.max(ans, right - left + 1);
}
right++;
}
}
return ans;
}
}
- 时间复杂度:枚举 26 种可能性,每种可能性会扫描一遍数组。复杂度为 O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
解题突破口
当采用常规的分析思路发现无法进行时,要去关注一下数据范围中「数值小」的值。因为数值小其实是代表了「可枚举」,往往是解题或者降低复杂度的一个重要(甚至是唯一)的突破口。
本篇博客详细介绍了如何使用滑动窗口解决LeetCode 395题,讨论了解题策略,包括枚举字符种类数目,维护滑动窗口边界,并解释了为何需要先枚举26种可能性以确保算法的单调性。通过这种方法,可以在O(n)的时间复杂度和O(n)的空间复杂度下找到至少有K个重复字符的最长子串。

970

被折叠的 条评论
为什么被折叠?



