LeetCode题练习与总结:至少有 K 个重复字符的最长子串--395

一、题目描述

给你一个字符串 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

二、解题思路

这个问题可以使用分治法的思想来解决。首先,我们可以遍历字符串,统计每个字符的出现次数。然后,找出那些出现次数少于k的字符,因为这些字符不能出现在结果子串中。我们将字符串根据这些字符分割成多个子串,然后递归地在每个子串中寻找满足条件的最长子串。

具体步骤如下:

  1. 统计字符串中每个字符的出现次数。
  2. 找出所有出现次数少于k的字符。
  3. 根据这些字符将原字符串分割成多个子串。
  4. 对每个子串递归地执行上述步骤,直到找到满足条件的最长子串。

三、具体代码

java 复制代码
class Solution {
    public int longestSubstring(String s, int k) {
        // 如果字符串为空或者长度小于k,直接返回0
        if (s == null || s.length() < k) return 0;
        
        // 统计每个字符的出现次数
        int[] counts = new int[26];
        for (char c : s.toCharArray()) {
            counts[c - 'a']++;
        }
        
        // 遍历每个字符,找出出现次数少于k的字符
        for (int i = 0; i < 26; i++) {
            if (counts[i] > 0 && counts[i] < k) {
                // 找到出现次数少于k的字符,分割字符串
                int res = 0;
                for (String str : s.split(String.valueOf((char)(i + 'a')))) {
                    // 递归处理每个子串
                    res = Math.max(res, longestSubstring(str, k));
                }
                return res;
            }
        }
        
        // 如果所有字符的出现次数都不少于k,返回原字符串长度
        return s.length();
    }
}

这段代码首先统计了字符串中每个字符的出现次数,然后检查哪些字符的出现次数少于k。对于每个这样的字符,它将字符串分割成多个子串,并对每个子串递归地调用longestSubstring函数。最终,它会返回找到的最长子串的长度。如果字符串中的所有字符都至少出现了k次,那么它会直接返回字符串的长度。

四、时间复杂度和空间复杂度

1. 时间复杂度
  • 统计每个字符的出现次数:这个步骤需要遍历字符串一次,所以时间复杂度是O(n),其中n是字符串的长度。

  • 遍历字符统计数组:这个步骤需要遍历一个固定大小(26)的数组,所以时间复杂度是O(1)。

  • 分割字符串:在最坏的情况下,如果字符串中的每个字符都至少出现了一次,那么我们需要分割n个子串(每个字符对应一个子串),每个子串的分割操作是O(n),所以这部分的时间复杂度是O(n^2)。

  • 递归处理子串:对于每个子串,我们都需要调用longestSubstring函数。在最坏的情况下,每次递归调用都会将字符串分割成两个部分,所以递归的深度是O(log n)。每个递归调用的处理时间是O(n),所以递归部分的总时间复杂度是O(n log n)。

综合以上步骤,我们可以得出总的时间复杂度是O(n^2) + O(n log n),由于O(n^2)是主导项,所以总的时间复杂度是O(n^2)。

2. 空间复杂度
  • 字符统计数组:这个数组的大小是固定的,为26,所以空间复杂度是O(1)。

  • 递归栈空间:在最坏的情况下,递归的深度是O(log n),所以递归栈的空间复杂度是O(log n)。

  • 分割字符串:在分割字符串时,会创建新的字符串对象,这些对象的空间复杂度是O(n)。

综合以上步骤,我们可以得出总的空间复杂度是O(n) + O(log n),由于O(n)是主导项,所以总的空间复杂度是O(n)。

五、总结知识点

  • 基础编程概念:

    • if 语句:用于条件判断。
    • for 循环:用于遍历集合或重复执行代码块。
    • return 语句:用于从方法中返回值。
  • 字符串操作:

    • toCharArray():将字符串转换为字符数组。
    • split(String regex):根据给定的正则表达式分割字符串,并返回分割后的字符串数组。
  • 数组:

    • 声明和初始化数组:int[] counts = new int[26];。
    • 访问数组元素:counts[c - 'a']++;。
  • 字符与ASCII码:

    • 字符与整数之间的转换:通过'a'来计算字符在数组中的索引。
  • 递归:

    • 方法自身调用自身:longestSubstring(str, k)。
  • 字符串分割与递归结合:

    • 使用split方法将字符串分割成多个子串,并对每个子串递归调用相同的逻辑。
  • 数学运算:

    • Math.max(int a, int b):返回两个整数中的最大值。
  • 算法设计:

    • 分治法:将大问题分解为小问题,递归地解决小问题,然后合并结果。
  • 边界条件处理:

    • 检查字符串是否为空或者长度是否小于k,并返回0。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。

相关推荐
薛定e的猫咪3 分钟前
(ICLR 2026)DF-META:面向 FJSP 的无导数元学习实例自适应调度
人工智能·深度学习·学习·算法·机器学习
Thinker QAQ13 分钟前
并发编程(十一):Shared Memory 与 Message Passing——关系与组合使用
java·python·go·并发编程·shared memory·message passing
齐 飞38 分钟前
idea中git误操作撤回git reset --hard ‘HEAD@{1}‘
java·git·intellij-idea
你的保护色1 小时前
多AS互联BGP4综合实验设计
java
某不知名網友1 小时前
ROS2进阶:服务通信、自定义SRV接口、参数服务器与基于服务的参数通信详解
java·开发语言·microsoft
Wang's Blog1 小时前
Java 项目实战: 交友项目 - MongoDB 入门:体系结构和命令行操作
java·mongodb·交友
小溪学编程1 小时前
将 Bean 放入 Spring 容器中的方式有哪些?
java·后端·spring
java_nnnn1 小时前
LeetCode刷题滑动窗口(水果成篮+找到字符串中所有字母的异位词)Java
java·开发语言·leetcode
用户3721574261352 小时前
Java 读取、修改和清空 PDF 文档属性(元数据)
java
SL_staff2 小时前
从‘改一行代码等三天’到‘改完即生效’:实施工程师的逻辑引擎避坑手记
java·产品·全栈