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。

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

相关推荐
李菠菜6 分钟前
非SpringBoot环境下Jedis集群操作Redis实战指南
java·redis
不当菜虚困19 分钟前
JAVA设计模式——(四)门面模式
java·开发语言·设计模式
BB_CC_DD22 分钟前
四. 以Annoy算法建树的方式聚类清洗图像数据集,一次建树,无限次聚类搜索,提升聚类搜索效率。(附完整代码)
深度学习·算法·聚类
m0Java门徒26 分钟前
面向对象编程核心:封装、继承、多态与 static 关键字深度解析
java·运维·开发语言·intellij-idea·idea
无心水1 小时前
【Java面试笔记:基础】8.对比Vector、ArrayList、LinkedList有何区别?
java·笔记·面试·vector·arraylist·linkedlist
创码小奇客1 小时前
MongoDB 时间序列:解锁数据时光机的终极指南
java·mongodb·trae
黯_森1 小时前
Java面向对象
java·后端
代码小侦探1 小时前
Java中以Maven方式引入Oracle JDBC Driver依赖的详解
java·oracle·maven
不畏惧的少年1 小时前
AQS的底层实现原理
java
洛小豆2 小时前
饭票、图书馆、GC:这样理解 Java 引用,谁还不会?
java·后端·面试