跳至主要內容

395. 至少有 K 个重复字符的最长子串


395. 至少有 K 个重复字符的最长子串

🟠   🔖  哈希表 字符串 分治 滑动窗口  🔗 力扣open in new window LeetCodeopen in new window

题目

Given a string s and an integer k, return the length of the longest substring of s such that the frequency of each character in this substring is greater than or equal to k.

if no such substring exists, return 0.

Example 1:

Input: s = "aaabb", k = 3

Output: 3

Explanation: The longest substring is "aaa", as 'a' is repeated 3 times.

Example 2:

Input: s = "ababbc", k = 2

Output: 5

Explanation: The longest substring is "ababb", as 'a' is repeated 2 times and 'b' is repeated 3 times.

Constraints:

  • 1 <= s.length <= 10^4
  • s consists of only lowercase English letters.
  • 1 <= k <= 10^5

题目大意

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

如果不存在这样的子字符串,则返回 0。

示例 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

解题思路

  1. 字符种类限制的优化策略

    • 不必暴力枚举所有子串,而是以 字符种类数量 (targetUnique) 为目标,从 1 到字符串中实际字符种类数进行滑动窗口遍历。
    • charLens 中去重字符的数量,通过 new Set(s.split('')) 来计算。
  2. 滑动窗口机制

    • 左右双指针 (leftright):动态控制子串范围。
    • 字符计数 (charCount) 哈希表:记录当前窗口内字符的出现次数。
    • 状态变量 (uniqueCount, countAtLeastK)
      • uniqueCount:窗口中不同字符的数量。
      • countAtLeastK:出现次数至少为 k 的字符数量。
  3. 窗口调整规则

    • 每次向右扩展窗口时更新字符计数和状态变量。
    • 如果 uniqueCount > targetUnique,说明窗口内字符种类超出目标范围,需要移动左指针收缩窗口。
    • 如果 uniqueCount == countAtLeastK,更新结果长度。

复杂度分析

  • 时间复杂度O(26 * n)
    • 外层循环最多 26 次(针对每种字符种类数量)。
    • 内层滑动窗口处理时间复杂度为 O(n)
  • 空间复杂度O(26),用于字符计数哈希表 charCount

代码

/**
 * @param {string} s
 * @param {number} k
 * @return {number}
 */
var longestSubstring = function (s, k) {
	if (!s || s.length === 0) return 0;
	let maxLen = 0;
	const n = s.length;
	const charLen = new Set(s.split('')).size;

	for (let targetUnique = 1; targetUnique <= charLen; targetUnique++) {
		let left = 0,
			right = 0;
		const charCount = new Map();
		let uniqueCount = 0,
			countAtLeastK = 0;

		while (right < n) {
			// 扩展窗口
			const charRight = s[right];
			charCount.set(charRight, (charCount.get(charRight) || 0) + 1);
			if (charCount.get(charRight) === 1) uniqueCount++;
			if (charCount.get(charRight) === k) countAtLeastK++;
			right++;

			// 收缩窗口
			while (uniqueCount > targetUnique) {
				const charLeft = s[left];
				if (charCount.get(charLeft) === k) countAtLeastK--;
				charCount.set(charLeft, charCount.get(charLeft) - 1);
				if (charCount.get(charLeft) === 0) uniqueCount--;
				left++;
			}

			// 更新结果
			if (uniqueCount === countAtLeastK) {
				maxLen = Math.max(maxLen, right - left);
			}
		}
	}

	return maxLen;
};

相关题目

题号标题题解标签难度力扣
2014重复 K 次的最长子序列贪心 字符串 回溯 2+🔴🀄️open in new window 🔗open in new window
2067等计数子串的数量 🔒字符串 计数 前缀和🟠🀄️open in new window 🔗open in new window
2405子字符串的最优划分[✓]贪心 哈希表 字符串🟠🀄️open in new window 🔗open in new window
2958最多 K 个重复元素的最长子数组数组 哈希表 滑动窗口🟠🀄️open in new window 🔗open in new window
2981找出出现至少三次的最长特殊子字符串 I哈希表 字符串 二分查找 2+🟠🀄️open in new window 🔗open in new window
2982找出出现至少三次的最长特殊子字符串 II哈希表 字符串 二分查找 2+🟠🀄️open in new window 🔗open in new window