395. 至少有 K 个重复字符的最长子串 分治或者滑动窗口

思路

  1. 分治的解法

    • 明白这么的一个基本的事实,给定一个字符串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官方的分析:

      官方分析

  2. 滑动窗口的解法

    • 首先枚举最长的字符串中字符的种类的个数,我们这里的字符集最多有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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值