# [395. Longest Substring with At Least K Repeating Characters](https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/) # 思路 给定一个只包含小写字母的字符串s和整数k,求s的子串中满足子串中的字母出现次数都不少于k的子串的最大长度。 ## 思路一、暴力 此题可以用暴力解法,但是直接暴力可能会超时,需要稍微改进一点。我们如何判断每个字母出现次数是否不小于k呢, 如果我们用一个长为26的数组cnt记录每个字母的出现次数,然后需要每次遍历一遍这个数组来判断是否满足提交,效率不高。 由于只可能是26个小写字母,所以我们新增一个32位整数mask来记录某个字母是否是满足条件的, 若某一位为0则对应字母是满足条件的(即要么没这个字母要么这个字母出现次数不小于k),所以我们只需判断整个mask是否为0就可以判断是否全部满足条件了。 具体来讲,我们用两层循环,第一层循环为子串的左边界start,第二层循环则是子串的右边界end。 每次进入第二层循环时都需要初始化数组cnt和mask,在第二层循环内部我们需要更新mask并判断mask是否为0,若是即可尝试更新结果。 时间复杂度O(n^2),空间复杂度O(1)。 > **如果只有小(大)写字母,即只有26个字符,那么用一个32为整数来进行记录一些bool状态是很常见的思路。** ## 思路二、分治 此题可以考虑用分治法。那么要如何进行划分呢,由于要满足出现次数不小于k,我们可以考虑先统计每个字母的出现次数, 出现次数小于k的那些字母肯定不可能出现在合法子串中的,即可以将这些字母作为分界线对字符串s进行划分。递归出口就是每次字母出现次数都不小于k。 这应该是此题最好写的方法,亲测也是最快的(0ms)。 ## 思路二、双指针 因为题目中限定了字符串中只有26个小写字母,所以最后满足题意的子串中的unique字母数一定是在范围[1, 26]内, 我们可以从1到26枚举unique字母数(记为cnt),对于每个cnt,维护一个窗口左右指针start和end,我们不断更新end和start,保持窗口内的子串的unique字符数(记为uniqueCnt)不超过cnt, 为此需要用一个大小为26的数组charCnt来记录窗口内每个字母的出现次数。 怎样更新start和end呢?我们让end不断加1更新,而start的更新需要视情况而定: > 先判断end字符是否是一个全新的字符,若是那么需要让uniqueCnt加1,此时可能uniqueCnt超过cnt了,所以我们需要将start右移直到uniqueCnt不超过cnt。 每次更新好end和start后还需要判断窗口内每个字符串是否满足条件,若是应该尝试更新res。 外层循环固定26次,内存循环中end和start都是只可能往右不可能往左的,所以是线性复杂度的,所以总的时间复杂度也是线性的。 # C++ ## 思路一 ``` C++ class Solution { public: int longestSubstring(string s, int k) { int start = 0, res = 0; for(int start = 0; start < s.size(); start++){ int mask = 0; vectorcnt(26, 0); for(int end = start; end < s.size(); end++){ int c = s[end] - 'a'; if(++cnt[c] >= k) mask &= (~(1 << c)); // 将对应位置0 else mask |= (1 << c); // 将对应位置1 if(!mask) res = max(res, end - start + 1); } } return res; } }; ``` ## 思路二 ``` C++ class Solution { public: int longestSubstring(string s, int k) { int res = 0, n = s.size(); vectorcnt(26, 0); // 统计每个字母的出现次数 for(auto &c: s) cnt[c - 'a']++; int start = 0; bool tag = true; for(int i = 0; i < n; i++){ if(cnt[s[i] - 'a'] < k){ tag = false; if(i > start) res = max(res, longestSubstring(s.substr(start, i - start), k)); start = i + 1; } } // tag为true说明每个字母出现次数均不小于k return tag ? s.size() : max(res, longestSubstring(s.substr(start, n - start), k)); } }; ``` ## 思路三 ``` C++ class Solution { public: int longestSubstring(string s, int k) { int res = 0, n = s.size(); for (int cnt = 1; cnt <= 26; ++cnt) { int start = 0, end = 0, uniqueCnt = 0; vector charCnt(26); for(; end < n; end++){ if (charCnt[s[end] - 'a']++ == 0) uniqueCnt++; while (uniqueCnt > cnt) if (--charCnt[s[start++] - 'a'] == 0) uniqueCnt--; bool valid = true; for (int j = 0; j < 26; ++j) if (charCnt[j] > 0 && charCnt[j] < k) {valid = false; break;} if(valid) res = max(res, end - start + 1); } } return res; } }; ```