【LeetCode-395】至少有K个重复字符的最长子串
问题
给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。
示例
输入: s = "ababbc", k = 2
输出: 5
解释: 最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。
解答1:滑动窗口
class Solution {
public:
int longestSubstring(string s, int k) {
int res = 0;
for (int i = 1; i < 27; i++) { // 遍历窗口中可能存在的字符种类数量
int m[26] = {0}; // 统计每个字符出现的次数
int word_cnt = 0, ok_cnt = 0; // 分别为字符种类数、满足条件的字符种类数
int left = 0, right = 0;
while (right < s.size()) {
m[s[right] - 'a']++;
if (m[s[right] - 'a'] == 1) word_cnt++;
if (m[s[right++] - 'a'] == k) ok_cnt++;
while (word_cnt > i) { // word_cnt大于i时left不可能大于等于right
if (m[s[left] - 'a'] == 1) word_cnt--;
if (m[s[left] - 'a'] == k) ok_cnt--;
m[s[left++] - 'a']--;
}
if (word_cnt == ok_cnt) res = max(res, right - left); // 窗口中字符满足要求时,比较长度
}
}
return res;
}
};
重点思路
本题要求满足某个条件的最大字符串长度,第一时间想到用滑动窗口做。能使用滑动窗口法的题目必须具备一个特点:一定存在一个对窗口的限制,迫使左指针右移。本题的限制为窗口中的字符最小重复次数为k。但是,当前窗口中出现次数小于k的字符,在右指针向右移动的过程中可能又满足这个条件了,所以我们不知道到底什么时候移动左指针。
这个时候,我们从字符串自身的特性开始考虑:字母的种类只有26种。当题目给出的要求不足以限制左指针的移动时,我们人为地给滑动窗口加一个限制:滑动窗口中字符数量不超过i
,i
的取值范围为1~26。因为给滑动窗口加的是另外的限制条件,此时滑动窗口中的字符串不一定满足题目的要求,所以我们在更新结果的时候要额外加上题目的要求。
解答2:分治
class Solution {
public:
int longestSubstring(string s, int k) {
int n = s.size(), pos = 0, m[26] = {0};
if (n < k) return 0; // 递归终止条件1:s长度小于k
for (char w : s) // 统计s中所有字符出现的次数
m[w - 'a']++;
while (pos < n && m[s[pos] - 'a'] >= k) // 满足题目要求的字符
pos++;
if (pos == n) return n; // 递归终止条件2:s满足题目要求
int left = longestSubstring(s.substr(0, pos), k); // 分治左边
while (pos < n && m[s[pos] - 'a'] < k) // 不满足题目要求的字符,剪枝
pos++;
int right = longestSubstring(s.substr(pos), k); // 分治右边
return max(left, right); // 返回左右的最大值
}
};
重点思路
分治算法的本质是递归,将字符串从1个位置切开,分别递归左边和右边。上述算法对分治算法进行了剪枝,