diff --git a/solutions/395. Longest Substring with At Least K Repeating Characters.md b/solutions/395. Longest Substring with At Least K Repeating Characters.md new file mode 100644 index 0000000..99f34dc --- /dev/null +++ b/solutions/395. Longest Substring with At Least K Repeating Characters.md @@ -0,0 +1,115 @@ +# [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; + } +}; +``` +