思路
-
分治的解法
-
明白这么的一个基本的事实,给定一个字符串s,如果s中存在一个字符c,这个字符c在整个s中出现的次数<k,那么我们所要求得最长字串必定不包含这个这个字符c,于是我们便可以把这个字符串从c这个字符开始分成若干个部分,对这个若干个部分重复上面得操作,进行分治。
-
和一般分治得一点变形就是这个分治划分子问题的方法,需要想到这一点。
-
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:6.4 MB, 在所有 C++ 提交中击败了60.95%的用户
-
时间复杂度,如果预先知道所有可能出现的字符,对不同的字符出现的次数进行前缀和的统计,这个时间复杂度是O(n), 使用map记录[left,right]范围内每个字符出现的次数,对每一个字符在map中查询的时间复杂度最多为O(nlogn),因此总的时间复杂度大约在O(nlogn).
-
leetcode官方的分析:
-
-
滑动窗口的解法
-
首先枚举最长的字符串中字符的种类的个数,我们这里的字符集最多有26个元素,首先我们只需要统计所有的字符种类的个个数为types, 然后从[types,1]枚举type,对于每一个type,进行滑动窗口的求解,滑动窗口内维护字符种类为type的最大的窗口,对于每一个这样的[left,right],都是这个type下的最大的窗口长度,于是我们遍历所有的这样的滑动窗口,就可以找到我们的解。
-
更详细的解析可以看leetcode的官方题解,代码写的也更加的简洁一些。
-
时间复杂度: O(m*n)
-
官方的代码写的更加简洁一点。
-
分治的代码
class Solution {
public:
int n, k;
int res = 0;
string s;
void slice(int left, int right) {
if (left > right) {
return;
}
map<char, int>cs;
for (int i = left; i <= right; i++) {
// 可以建立有限字符表出现个数的前缀和
if (cs.count(s[i]) == 0) {
cs[s[i]] = 1;
}
else {
cs[s[i]]++;
}
}
// 找不不满足条件的字符
set<char>sc;
for (auto& it : cs) {
if (it.second < k) {
sc.insert(it.first);
}
}
// 分治
if (sc.empty()) {
res = max(res, right - left + 1);
// return right-left+1;
return;
}
int last = left;
for (int i = left; i <= right; i++) {
if (sc.count(s[i])) {
slice(last, i - 1);
last = i + 1;
}
}
// 最后的一个子序列需要处理
if (last != right + 1) {
slice(last, right);
}
}
int longestSubstring(string s, int k) {
n = s.size();
this->s = s;
this->k = k;
slice(0, n - 1);
return res;
}
};
滑动窗口的解法
执行用时: 28 ms
内存消耗: 9.6 MB
#include<string>
#include<cstring>
#include<map>
#include<set>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
// 每一个最大滑动窗口的结构体
struct Node {
int left, right;
map<char, int>mp;//
Node(int _left, int _right, map<char, int>_mp) {
left = _left;
right = _right;
mp = _mp;
}
};
class Solution {
public:
int n, k;
string s;
int longestSubstring(string s, int k) {
if (k <= 0) {
return 0;
}
n = s.size();
if (k == 1) {
return n;
}
if (n == 1) {
return 0;
}
this->s = s;
this->k = k;
map<char, int>mp;
queue<Node>q;
// 统计字符串中的元素的个数,bbaaacbd
int has[26] = { 0 };
for (int i = 0; i < n; i++) {
if (!has[(int)(s[i] - 'a')]) {
has[(int)(s[i] - 'a')] = 1;
}
}
int types = 0;
for (int i = 0; i < 26; i++) {
if (has[i])types++;
}
for (int type = types; type >= 1; type--) {//
int left = 0, right = 1, kind = 1;
mp.clear();
mp[s[left]] = 1; // mp中维护当前最大的[left,right-1]的窗口内每一个字符的个数
while (right < n) {
while (kind < type && right < n) {
if (mp.count(s[right])) {
mp[s[right]]++;
right++;
}
else {
mp[s[right]] = 1;
kind++;
right++;
}
}
if (kind == type) {
while (right < n && mp.count(s[right])) {// 扩大窗口
mp[s[right]]++;
right++;
}
q.push(Node(left, right - 1, map<char, int>(mp)));// 插入节点
kind--;// 减少一种字符,右移滑动窗口
while (left < right && mp[s[left]] >= 1) { // 右移left
mp[s[left]]--;
if (mp[s[left]] == 0) {
mp.erase(s[left]);
left++;
break;
}
left++;
}
}
}
}
int res = 0;
while (!q.empty()) { // 寻找长度最大的滑动窗口
int curres = 0;
Node node = q.front();
q.pop();
int ok = true;
for (auto& it : node.mp) {
char c = it.first;
int num = it.second;
if (num < k) {
ok = false;
break;
}
}
if (ok == true) {
res = max(res, node.right - node.left + 1);
// 不可以提前跳出
}
}
return res;
}
};
int main() {
string s = "zzzzzzzzzzaaaaaaaaabbbbbbbbhbhbhbhbhbhbhicbcbcibcbccccccccccbbbbbbbbaaaaaaaaafffaahhhhhiaahiiiiiiiiifeeeeeeeeee";
// bcbccccccccccbbbbbbbb
// 47-67
Solution sol;
cout << sol.longestSubstring(s, 10) << endl;
//cout << sol.res << endl;
return 0;
}