LeetCode/solutions/395. Longest Substring with At Least K Repeating Characters.md

116 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# [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;
vector<int>cnt(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();
vector<int>cnt(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<int> 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;
}
};
```