1. 问题描述:
给你一个字符串 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 <= 10 ^ 4
s 仅由小写英文字母组成
1 <= k <= 10 ^ 5
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-with-at-least-k-repeating-characters
2. 思路分析:
因为涉及到一个区间的字符串长度,所以我们考虑使用双指针算法,但是由题目可知简单求解一个区间字符串是否满足题目要求是很难确定当前的区间是否是有解的,因为往右扩展的时候增加一个字符但是不知道增加的字符是否对结果是有利的,而且删除字符也是类似的,结果都是未知的也就无法确定满足要求的区间,这样对应的两个指针是没有单调性的,使用双指针算法具有单调性的条件是一个指针i往右走的时候,另外一个指针j单调往右走,为了解决指针单调性的问题我们可以增加一个条件使得指针是具有单调性的,我们每一次可以枚举当前区间中最多不同字符的个数k,这样当我们的区间不同字符的个数大于了k之后那么一个指针j就可以往右走了,因为往右走的时候才能够使得不同字符的数目减少,增加多一个这样的条件那么就可以使得指针具有单调性,这样由两个指针确定的区间就是有确定解的,因为字母总共有26个,所以在最外层的时候最多枚举26次,枚举的时候肯定答案是包含在里面的,所以最终一定可以求解出最优解,在遍历的过程中我们需要维护两个int类型的变量x, y,x表示当前不同字符的个数,y表示符合条件的字符的个数(符合条件指的是字符出现的次数是大于等于最少的次数K的,也即题目一开始给出的k)在遍历的时候使用哈希表(python使用字典来记录字符出现的次数)来维护字符出现的次数,在增加和删除字母的时候更新哈希表中字符的次数以及x, y的值即可,当x == y也即当前不同字符的数目等于满足条件的字符数目的时候那么就可以更新答案了。(感觉这道题目增加条件使得指针具有单调性的思路非常巧妙)
3. 代码如下:
import collections
class Solution:
def add(self, count: collections.defaultdict, c: str, x: int, y: int, K: int):
if count[c] == 0:
x += 1
if count[c] == K - 1:
y += 1
count[c] += 1
return x, y
def delete(self, count: collections.defaultdict, c: str, x: int, y: int, K: int):
if count[c] == 1: x -= 1
if count[c] == K:
y -= 1
count[c] -= 1
return x, y
# 枚举区间中最多包含k个不同的字符的时候那么就有了单调性了就可以使用双指针算法
def longestSubstring(self, s: str, K: int) -> int:
res = 0
for k in range(1, 27):
count = collections.defaultdict(int)
n = len(s)
# i表示后面的指针,j表示前面的指针
i, j = 0, 0
# x表示不同字符的数量, y表示符合要求的字符数目
x, y = 0, 0
while i < n:
# 因为需要更新x, y所以在方法中返回x, y的值这样可以更新x, y的值
x, y = self.add(count, s[i], x, y, K)
# 需要在循环中删除左边的字符使得不同字符的数目是小于等于k的
while x > k:
x, y = self.delete(count, s[j], x, y, K)
j += 1
if x == y:
res = max(res, i - j + 1)
i += 1
return res