leetcode
leetcode 351 ~ 400
至少有 K 个重复字符的最长子串

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

难度:

标签:

题目描述

给你一个字符串 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 <= 104
  • s 仅由小写英文字母组成
  • 1 <= k <= 105

代码结果

运行时间: 40 ms, 内存: 0.0 MB


/*
 * 思路:
 * 1. 使用Stream API实现递归方法。
 * 2. 统计字符串中每个字符的出现次数。
 * 3. 找到出现次数少于k次的字符作为分隔符,分割字符串。
 * 4. 对每个子串递归调用函数,并返回满足条件的最长子串长度。
 */
 
import java.util.Map;
import java.util.stream.Collectors;
 
public class Solution {
    public int longestSubstring(String s, int k) {
        if (s == null || s.length() == 0 || k > s.length()) return 0;
        return helper(s, k);
    }
 
    private int helper(String s, int k) {
        if (s.length() < k) return 0;
        Map<Character, Long> freq = s.chars()
                                    .mapToObj(c -> (char) c)
                                    .collect(Collectors.groupingBy(c -> c, Collectors.counting()));
        for (char c : s.toCharArray()) {
            if (freq.get(c) < k) {
                return s.chars()
                        .mapToObj(i -> (char) i)
                        .collect(Collectors.joining())
                        .split(String.valueOf(c)).length;
            }
        }
        return s.length();
    }
}
 

解释

方法:

这个题解采用了分治的思想。首先将字符串 s 放入栈中。然后不断从栈中取出子串,统计每个字符出现的次数。如果所有字符出现次数都不小于 k,则更新目前为止找到的最长子串长度。否则,找到出现次数小于 k 的字符,以它为分隔符将子串分割成更短的子串,放入栈中继续处理。这样递归处理下去,直到栈为空,即可找到最长的满足条件的子串。

时间复杂度:

O(n^2)

空间复杂度:

O(n)

代码细节讲解

🦆
在分治方法中,为什么选择以出现次数小于k的字符为分隔符进行分割?
在这个问题中,目标是找到所有字符都至少出现k次的最长子串。如果一个字符在某个子串中的出现次数小于k,那么这个字符不可能出现在满足条件的最长子串中。因此,以这些出现次数小于k的字符为分隔符分割字符串是合理的,因为它们标志着不可能属于有效子串的边界。这种分割方式帮助我们忽略那些不满足条件的部分,将问题缩小到更小的子串上,这是分治策略的核心。
🦆
在题解中提到每个字符最多会被放入和弹出栈一次,这是否意味着每个子串只被处理一次?如果是这样,如何保证处理了所有可能的子串?
实际上,每个子串可能会因为不同的字符而被多次分割和处理。题解中的表述可能有误,因为每个子串在被分割时,如果有多个不同的字符频率小于k,就会在每个这样的字符处被分割,从而产生新的子串。每个由这种方式产生的子串都会被单独处理。这确保了所有可能的子串都被考虑到,因为每次分割都是基于当前子串中不满足条件的字符进行的。
🦆
题解中使用栈来存储待处理的子串,是否可以使用其他数据结构如队列或链表来改善性能或逻辑清晰度?
使用队列或链表代替栈在逻辑上是可行的,且可能改善程序的理解度。使用队列,将遵循先进先出的顺序,可能使得算法的执行更符合从原始字符串开始逐步细化的直观逻辑。而使用链表,可以灵活地在任意位置添加或删除子串,这可能有助于管理复杂的分割情况。每种数据结构的选择都依赖于具体实现的需求和性能优化。
🦆
在处理过程中,如果一个长字符串的多个位置字符频率都小于k,是否会导致重复分割和重复处理?如果会,如何优化以避免这种情况?
是的,多个不同的字符频率小于k确实可能导致重复分割和处理,特别是当这些字符分布在不同位置时。为了优化这种情况,可以在分割前先对整个子串进行一次扫描,统计所有字符的出现次数,然后一次性去除所有出现次数小于k的字符。这样,每个子串在分割时可以减少重复的分割操作,从而提高效率。此外,使用一个高效的数据结构来维护字符计数,如哈希表,可以进一步提高处理速度。

相关问题