Find the length of the longest substring T of a given string (consists of lowercase letters only) such that every character in T appears no less than k times.

Example 1:

Input:
s = "aaabb", k = 3 Output:
3 The longest substring is "aaa", as 'a' is repeated 3 times.

Example 2:

Input:
s = "ababbc", k = 2 Output:
5 The longest substring is "ababb", as 'a' is repeated 2 times and 'b' is repeated 3 times.

这道题给了我们一个字符串s和一个正整数k,让求一个最大子字符串并且每个字符必须至少出现k次。作为 LeetCode 第三次编程比赛的压轴题目,博主再一次没有做出来,虽然难度标识只是 Medium。后来在网上膜拜学习了大神们的解法,发现当时的没做出来的原因主要是卡在了如何快速的判断某一个字符串是否所有的字符都已经满足了至少出现k次这个条件,虽然博主也用 HashMap 建立了字符和其出现次数之间的映射,但是如果每一次都要遍历 HashMap 中的所有字符看其出现次数是否大于等于k,未免有些不高效。而用 mask 就很好的解决了这个问题,由于字母只有 26 个,而整型 mask 有 32 位,足够用了,每一位代表一个字母,如果为1,表示该字母不够k次,如果为0就表示已经出现了k次,这种思路真是太聪明了,隐约记得这种用法在之前的题目中也用过,但是博主并不能举一反三( 沮丧脸:( ),还得继续努力啊。遍历字符串,对于每一个字符,都将其视为起点,然后遍历到末尾,增加 HashMap 中字母的出现次数,如果其小于k,将 mask 的对应位改为1,如果大于等于k,将 mask 对应位改为0。然后看 mask 是否为0,是的话就更新 res 结果,然后把当前满足要求的子字符串的起始位置j保存到 max_idx 中,等内层循环结束后,将外层循环变量i赋值为 max_idx+1,继续循环直至结束,参见代码如下:

解法一:

class Solution {
public:
int longestSubstring(string s, int k) {
int res = , i = , n = s.size();
while (i + k <= n) {
int m[] = {}, mask = , max_idx = i;
for (int j = i; j < n; ++j) {
int t = s[j] - 'a';
++m[t];
if (m[t] < k) mask |= ( << t);
else mask &= (~( << t));
if (mask == ) {
res = max(res, j - i + );
max_idx = j;
}
}
i = max_idx + ;
}
return res;
}
};

虽然上面的方法很机智的使用了 mask 了标记某个子串的字母是否都超过了k,但仍然不是很高效,因为遍历了所有的子串,使得时间复杂度到达了平方级。来想想如何进行优化,因为题目中限定了字符串中只有字母,这意味着最多不同的字母数只有 26 个,最后满足题意的子串中的不同字母数一定是在 [1, 26] 的范围,这样就可以遍历这个范围,每次只找不同字母个数为 cnt,且每个字母至少重复k次的子串,来更新最终结果 res。这里让 cnt 从1遍历到 26,对于每个 cnt,都新建一个大小为 26 的数组 charCnt 来记录每个字母的出现次数,使用的思想其实还是滑动窗口 Sliding Window,使用两个变量 start 和 i 来分别标记窗口的左右边界,当右边界小于n时,进行 while 循环,需要一个变量 valid 来表示当前子串是否满足题意,初始化为 true,还需要一个变量 uniqueCnt 来记录子串中不同字母的个数。此时若 s[i] 这个字母在 charCnt 中的出现次数为0,说明遇到新字母了,uniqueCnt 自增1,同时把该字母的映射值加1。此时由于 uniqueCnt 变大了,有可能会超过之前限定了 cnt,所以这里用一个 while 循环,条件是当 uniqueCnt 大于 cnt ,此时应该收缩滑动窗口的左边界,那么对应的左边界上的字母的映射值要自减1,若减完后为0了,则 uniqueCnt 自减1,注意这里一会后加,一会先减的操作,不要搞晕了。当 uniqueCnt 没超过 cnt 的时候,此时还要看当前窗口中的每个字母的出现次数是否都大于等于k,遇到小于k的字母,则直接 valid 标记为 false 即可。最终若 valid 还是 true,则表示滑动窗口内的字符串是符合题意的,用其长度来更新结果 res 即可,参见代码如下:

解法二:

class Solution {
public:
int longestSubstring(string s, int k) {
int res = , n = s.size();
for (int cnt = ; cnt <= ; ++cnt) {
int start = , i = , uniqueCnt = ;
vector<int> charCnt();
while (i < n) {
bool valid = true;
if (charCnt[s[i++] - 'a']++ == ) ++uniqueCnt;
while (uniqueCnt > cnt) {
if (--charCnt[s[start++] - 'a'] == ) --uniqueCnt;
}
for (int j = ; j < ; ++j) {
if (charCnt[j] > && charCnt[j] < k) valid = false;
}
if (valid) res = max(res, i - start);
}
}
return res;
}
};

下面这种解法用的分治法 Divide and Conquer 的思想,看起来简洁了不少,但是个人感觉比较难想,这里使用了一个变量 max_idx,是用来分割子串的,实现开始统计好了字符串s的每个字母出现的次数,然后再次遍历每个字母,若当前字母的出现次数小于k了,则从开头到前一个字母的范围内的子串可能是满足题意的,还需要对前面的子串进一步调用递归,用返回值来更新当前结果 res,此时变量 ok 标记为 false,表示当前整个字符串s是不符合题意的,因为有字母出现次数小于k,此时 max_idx 更新为 i+1,表示再从新的位置开始找下一个出现次数小于k的字母的位置,可以对新的范围的子串继续调用递归。当 for 循环结束后,若 ok 是 true,说明整个s串都是符合题意的,直接返回n,否则要对 [max_idx, n-1] 范围内的子串再次调用递归,因为这个区间的子串也可能是符合题意的,还是用返回值跟结果 res 比较,谁大就返回谁,参见代码如下:

解法三:

class Solution {
public:
int longestSubstring(string s, int k) {
int n = s.size(), max_idx = , res = ;
int m[] = {};
bool ok = true;
for (char c : s) ++m[c];
for (int i = ; i < n; ++i) {
if (m[s[i]] < k) {
res = max(res, longestSubstring(s.substr(max_idx, i - max_idx), k));
ok = false;
max_idx = i + ;
}
}
return ok ? n : max(res, longestSubstring(s.substr(max_idx, n - max_idx), k));
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/395

类似题目:

Longest Substring with At Most K Distinct Characters

Longest Substring with At Most Two Distinct Characters

Longest Substring Without Repeating Characters

参考资料:

https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/

https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/discuss/87834/onlogn-recursive-cpp-solution

https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/discuss/87739/Java-Strict-O(N)-Two-Pointer-Solution

https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/discuss/87749/Two-short-C%2B%2B-solutions-(3ms-and-6ms)

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Longest Substring with At Least K Repeating Characters 至少有K个重复字符的最长子字符串的更多相关文章

  1. [LeetCode] 395. Longest Substring with At Least K Repeating Characters 至少有K个重复字符的最长子字符串

    Find the length of the longest substring T of a given string (consists of lowercase letters only) su ...

  2. [LeetCode] Longest Substring with At Most Two Distinct Characters 最多有两个不同字符的最长子串

    Given a string S, find the length of the longest substring T that contains at most two distinct char ...

  3. 395 Longest Substring with At Least K Repeating Characters 至少有K个重复字符的最长子串

    找到给定字符串(由小写字符组成)中的最长子串 T , 要求 T 中的每一字符出现次数都不少于 k .输出 T 的长度.示例 1:输入:s = "aaabb", k = 3输出:3最 ...

  4. [LeetCode] 159. Longest Substring with At Most Two Distinct Characters 最多有两个不同字符的最长子串

    Given a string s , find the length of the longest substring t  that contains at most 2 distinct char ...

  5. LeetCode OJ:Longest Substring Without Repeating Characters(最长无重复字符子串)

    Given a string, find the length of the longest substring without repeating characters. For example, ...

  6. LeetCode Longest Substring with At Most Two Distinct Characters

    原题链接在这里:https://leetcode.com/problems/longest-substring-with-at-most-two-distinct-characters/ 题目: Gi ...

  7. [LeetCode] 340. Longest Substring with At Most K Distinct Characters 最多有K个不同字符的最长子串

    Given a string, find the length of the longest substring T that contains at most k distinct characte ...

  8. [LeetCode]Longest Substring Without Repeating Characters题解

    Longest Substring Without Repeating Characters: Given a string, find the length of the longest subst ...

  9. [LeetCode] Longest Substring Without Repeating Characters 最长无重复子串

    Given a string, find the length of the longest substring without repeating characters. For example, ...

随机推荐

  1. Unity3d学习 相机的跟随

    最近在写关于相机跟随的逻辑,其实最早接触相机跟随是在Unity官网的一个叫Roll-a-ball tutorial上,其中简单的涉及了关于相机如何跟随物体的移动而移动,如下代码: using Unit ...

  2. Oracle数据库该如何着手优化一个SQL

    这是个终极问题,因为优化本身的复杂性实在是难以总结的,很多时候优化的方法并不是用到了什么高深莫测的技术,而只是一个思想意识层面的差异,而这些都很可能连带导致性能表现上的巨大差异. 所以有时候我们应该先 ...

  3. android 两种实现计时器时分秒的实现,把时间放在你的手中~

    可能我们在开发中会时常用到计时器这玩意儿,比如在录像的时候,我们可能需要在右上角显示一个计时器.这个东西其实实现起来非常简单. 只需要用一个控件Chronometer,是的,就这么简单,我都不好意思讲 ...

  4. CSS 3学习——transition 过渡

    以下内容根据官方规范翻译以及自己的理解整理. 1.介绍 这篇文档介绍能够实现隐式过渡的CSS新特性.文档中介绍的CSS新特性描述了CSS属性的值如何在给定的时间内平滑地从一个值变为另一个值. 2.过渡 ...

  5. AbpZero--2.如何启动

    1.直接启动 VS中直接启动 2.IIS站点 IIS中配置一个站点来启动(推荐) 3.登录 系统默认创建2个用户 默认用户名:admin 密码:123qwe 租户:Default  默认用户名:adm ...

  6. WebApi基于Token和签名的验证

    最近一段时间在学习WebApi,涉及到验证部分的一些知识觉得自己并不是太懂,所以来博客园看了几篇博文,发现一篇讲的特别好的,读了几遍茅塞顿开(都闪开,我要装逼了),刚开始读有些地方不理解,所以想了很久 ...

  7. C# 工厂模式+虚方法(接口、抽象方法)实现多态

    面向对象语言的三大特征之一就是多态,听起来多态比较抽象,简而言之就是同一行为针对不同对象得到不同的结果,同一对象,在不同的环境下得到不同的状态. 实例说明: 业务需求:实现一个打开文件的控制台程序的d ...

  8. 计算Div标签内Checkbox个数或已被disabled的个数

    先看下面的html: 计算div内的checkbox个数:$('#divmod input[type="checkbox"]').length 计算div内checkbox被dis ...

  9. Linux基础介绍【第三篇】

    更改SSH服务端远程登录的配置 windows服务端的默认远程管理端口是3389,管理员用户是administrator,普通用户是guest.Linux的管理用户是root,普通用户默认有很多个,远 ...

  10. crontab介绍

    1.Cron的启动与关闭 由于Cron是Linux的内置服务,可以用以下的方法启动.关闭这个服务: /sbin/service crond start           //启动服务/sbin/se ...