LeetCode 395. 至少有 K 个重复字符的最长子串(滑动窗口)

本篇博客详细介绍了如何使用滑动窗口解决LeetCode 395题,讨论了解题策略,包括枚举字符种类数目,维护滑动窗口边界,并解释了为何需要先枚举26种可能性以确保算法的单调性。通过这种方法,可以在O(n)的时间复杂度和O(n)的空间复杂度下找到至少有K个重复字符的最长子串。


题目描述

在这里插入图片描述

滑动窗口

枚举最长子串中的字符种类数目 k i n d ∈ [ 1 , 26 ] kind \in [1,26] kind[1,26],对于给定的字符种类数量 k i n d kind kind,维护滑动窗口的左右边界 l e f t , r i g h t left,right left,right、滑动窗口内部每个字符出现的次数 w i n d o w window window、滑动窗口内的字符种类数目 t o t a l total total 以及滑动窗口内满足「出现次数不少于 k」条件的字符种类数目 s a t _ t o t a l sat\_total sat_total,在滑动窗口的维护过程中,得到最长子串的大小。

「当确定了窗口内所包含的字符数量时,区间重新具有了二段性质」。这是本题的滑动窗口解法和迄今为止做的滑动窗口题目的最大不同,本题需要手动增加限制,即限制窗口内字符种类。

【补充】「为什么需要先枚举 26 种可能性」

首先我们知道「答案子串的左边界左侧的字符以及右边界右侧的字符一定不会出现在子串中,否则就不会是最优解」。

但如果我们只从该性质出发的话,朴素解法应该是使用一个滑动窗口,不断的调整滑动窗口的左右边界,使其满足「左边界左侧的字符以及右边界右侧的字符一定不会出现在窗口中」,这实际上就是双指针解法,但是如果不先敲定(枚举)出答案所包含的字符数量的话,这里的双指针是不具有单调性的。

换句话说,只利用这一性质是没法完成逻辑的。

这时候我们面临的问题是:性质是正确的,但是还无法直接利用。

因此我们需要先利用字符数量有限性(可枚举)作为切入点,使得「答案子串的左边界左侧的字符以及右边界右侧的字符一定不会出现在子串中」这一性质在双指针的实现下具有单调性。也就是「让区间重新具有二段性质」。

然后遍历 26 种可能性(答案所包含的字符种类数量),对每种可能性应用滑动窗口(由上述性质确保正确),可以得到每种可能性的最大值(局部最优),由所有可能性的最大值可以得出答案(全局最优)

class Solution {
    public int longestSubstring(String s, int k) {
        int n = s.length();
        int ans = 0;
        for (int kind = 1; kind <= 26; kind++) {
            // 滑动窗口左右边界
            int left = 0;
            int right = 0;
            // 窗口内部每个字符出现的次数
            int[] window = new int[26];
            // 窗口内字符种类
            int total = 0;
            // 满足「出现次数不少于 k」条件的,窗口内字符种类
            int sat_total = 0;

            while (right < n) {
                int cur = s.charAt(right) - 'a';
                // 右边界入窗
                window[cur]++;
                if (window[cur] == 1) {
                    // 当前字符首次入窗
                    total++;
                }
                if (window[cur] == k) {
                    // 当前字符在窗口内出现次数满足k个
                    sat_total++;
                }
                // 当窗口内字符种类数超过限制时,左边界收缩
                while (total > kind) {
                    int leftChar = s.charAt(left) - 'a';
                    if (window[leftChar] == 1) {
                        total--;
                    }
                    if (window[leftChar] == k) {
                        sat_total--;
                    }
                    window[leftChar]--;
                    left++;
                }
                // 当所有字符都符合要求,更新答案
                if (sat_total == total) {
                    ans = Math.max(ans, right - left + 1);
                }
                right++;
            }
        }
        return ans;
    }
}
  • 时间复杂度:枚举 26 种可能性,每种可能性会扫描一遍数组。复杂度为 O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

解题突破口

当采用常规的分析思路发现无法进行时,要去关注一下数据范围中「数值小」的值。因为数值小其实是代表了「可枚举」,往往是解题或者降低复杂度的一个重要(甚至是唯一)的突破口

Reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xylitolz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值