【LeetCode】至少有 K 个重复字符的最长子串 [M](滑动窗口)

166 篇文章 0 订阅
78 篇文章 41 订阅

395. 至少有 K 个重复字符的最长子串 - 力扣(LeetCode)

一、题目

给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。

示例 1:

输入:s = "aaabb", k = 3
输出:3
解释:最长子串为 "aaa" ,其中 'a' 重复了 3 次。

示例 2:​​​​​​​

输入:s = "ababbc", k = 2
输出:5
解释:最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。

提示:

  • 1 <= s.length <= 104
  • s 仅由小写英文字母组成
  • 1 <= k <= 105

二、代码

class Solution {
    public int longestSubstring(String str, int k) {
        // 转为字符数组
        char[] s = str.toCharArray();
        // 记录符合题目要求的最长子串长度
        int max = 0;
        // 枚举窗口内所有的字符种类数
        for (int kinds = 1; kinds <= 26; kinds++) {
            // 当前这里一轮窗口内a~z出现的次数
            int[] count = new int[26];
            // 窗口右边界,每一轮都从头开始
            int r = -1;
            // 目前窗口内收集了几种字符了
            int nowKinds = 0;
            // 目前窗口内出现次数>=k次的字符,满足了几种
            int satisfyKinds = 0;
            // l要尝试每一个位置作为窗口的最左边界
            for (int l = 0; l < s.length; l++) { // 右边界不回退
                // 窗口右扩的条件:
                //  1) 窗口内字符种数不足
                //  2)某个字符出现不足K次
                // 循环执行条件:r右移一位不能导致越界  并且如果右移一位新加入窗口一个全新的字符,超过了我们要求的kinds个种数了,那也不可以右扩
                while (r < s.length - 1 && !(nowKinds == kinds && count[s[r + 1] - 'a'] == 0)) { // 下一个词频为0,就是新出现的,算进来就超了                   
                    // 窗口右扩
                    r++;
                    // 如果新加入窗口的字符是第一次进入窗口,则将窗口内的字符种数++
                    if (count[s[r] - 'a'] == 0) {
                        nowKinds++;
                    }
                    // 如果新加入窗口的字符已经在窗口内出现了k-1次了,那么又加入一个就变成了出现k次。则将满足窗口内出现次数>=k次的字符种数++
                    if (count[s[r] - 'a'] == k - 1) {
                        satisfyKinds++;
                    }
                    // 将新加入的字符出现次数在当前窗口的词频表中++
                    count[s[r] - 'a']++;
                }
                // 如果当前满足窗口内出现次数>=k次的字符种数 等于 本轮枚举要求的窗口内字符种数
                if (satisfyKinds == kinds) {
                    // 尝试更新符合题目要求的最长子串长度
                    max = Math.max(max, r - l + 1);
                }
                // 窗口左边界右移,弹出一个字符
                count[s[l] - 'a']--;
                // 如果弹出字符后,这个字符在窗口内就没有了,就将窗口内的字符种数--
                if (count[s[l] - 'a'] == 0) {
                    nowKinds--;
                }
                // 如果弹出字符后,这个字符在窗口内出现次数不是k次了,变成了k-1次,就将窗口内满足窗口内出现次数>=k次的字符种数--
                if (count[s[l] - 'a'] == k - 1) {
                    satisfyKinds--;
                }
            }
        }
        // 返回答案
        return max;
    }
}

三、解题思路 

因为只有a~z小写字母,所以我们可以直接把所有字母的情况都枚举一遍,最多也就只有26个而已。

我在整个字符串中寻找:

  • 子串必须只包含一种字符,出现次数大于等于k次,满足这个条件的子串长度有多少,
  • 子串必须只包含两种字符,但每一种的出现次数都大于等于k次,满足这个条件的子串长度有多少,
  • 子串必须只包含三种字符,但每一种的出现次数都大于等于k次,满足这个条件的子串长度有多少,
  • ......

一直枚举到26种,在所有答案中求最大就是答案。

流程:

做窗口固定,例如我们就要求窗口中只有3种字符,如果你迟迟没有到达3种,你就右扩。

那当你扩到再往下一个就出现第4种了,停止扩充

然后看满足的种类数和要求的种类数是不是一样的, 如果不一样就是无效的

R往右移的逻辑:

  1. 种数不足
  2. 某个字符出现不足K次

接下来L++,出去一个字符,相应的调整count和种类。再去从新的左边界开始,看能不能找到一个新的满足窗口内只有3中字符并且都满足出现次数条件的子串。整个流程下来,就能找到只有3个字符并且符合条件的子串的最大长度了。

下面就接着去枚举窗口内有4种字符的情况。直到完成26个小写字母的全部枚举,从所有答案中选出最大值就是答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值