豆包MarsCode AI刷题(四)

52 阅读6分钟

今天给大家分享一下每日刷题的题目解析和经验分享,本系列预计会推出五期,今天给大家带来的是第四期的分享。

字符替换与最长子串问题

问题描述


小R得到了一个由大写字母组成的字符串,长度为 n。她可以对字符串中的字符进行修改,每次操作允许将某个位置的字符修改为任意字符。例如,字符串 ABC 的第一个字符 A 改为 B,则字符串变为 BBC。她最多可以进行 k 次这样的修改。

小R想知道,通过最多 k 次修改后,字符串中由最多两种不同字母组成的最长连续子串的长度是多少。

测试样例

样例1:

输入:n = 6 ,k = 1 ,inp = "ABCBAD"
输出:5

样例2:

输入:n = 5 ,k = 1 ,inp = "AEABD"
输出:4

样例3:

输入:n = 8 ,k = 2 ,inp = "AAAABBCD"
输出:8

解题思路

  1. 统计字符种类

    • 首先遍历字符串,统计所有出现的字符种类。这一步可以使用 HashSet 来实现,因为 HashSet 可以自动去重。
  2. 两两组合

    • 从统计的字符种类中选取两个字符进行两两组合。这一步可以通过双重循环来实现。
  3. 滑动窗口

    • 对于每种组合,使用滑动窗口来找到满足条件的最长子串。
    • 扩展窗口:不断移动 right 指针,扩展窗口,直到窗口内的字符种类超过两种。
    • 收缩窗口:当窗口内的字符种类超过两种时,移动 left 指针,收缩窗口,直到窗口内的字符种类再次满足条件。
    • 记录最大长度:在每次扩展和收缩窗口时,记录当前窗口的长度,并更新最大长度。

详细步骤

  1. 统计字符种类

    • 遍历字符串,将每个字符添加到 HashSet 中。
  2. 两两组合

    • 使用双重循环遍历 HashSet,选取两个不同的字符进行组合。
  3. 滑动窗口

    • 初始化:使用两个指针 left 和 right 来表示滑动窗口的左右边界。
    • 扩展窗口:不断移动 right 指针,扩展窗口,直到窗口内的字符种类超过两种。
    • 收缩窗口:当窗口内的字符种类超过两种时,移动 left 指针,收缩窗口,直到窗口内的字符种类再次满足条件。
    • 记录最大长度:在每次扩展和收缩窗口时,记录当前窗口的长度,并更新最大长度。
public static int solution(int n, int k, String inp) {
       // 统计字符种类
       Set<Character> charSet = new HashSet<>();
       for (char c : inp.toCharArray()) {
           charSet.add(c);
       }
       
       // 初始化最大长度
       int maxLength = 0;
       // 排除特殊情况
       if (charSet.size()<=1) {
        return 1;
       }
       // 两两组合
       Character[] chars = charSet.toArray(new Character[0]);
       for (int i = 0; i < chars.length; i++) {
           for (int j = i + 1; j < chars.length; j++) {
               char char1 = chars[i];
               char char2 = chars[j];
               
               // 使用滑动窗口
               int left = 0;
               int right = 0;
               int count1 = 0;
               int count2 = 0;
               int modifications = 0;
               
               while (right < n) {
                   char rightChar = inp.charAt(right);
                   
                   if (rightChar == char1) {
                       count1++;
                   } else if (rightChar == char2) {
                       count2++;
                   } else {
                       // 如果不是这两个字符之一,则需要修改
                       modifications++;
                   }
                   
                   // 检查修改次数是否超过k
                   while (modifications > k) {
                       char leftChar = inp.charAt(left);
                       if (leftChar == char1) {
                           count1--;
                       } else if (leftChar == char2) {
                           count2--;
                       } else {
                           modifications--;
                       }
                       left++;
                   }
                   
                   // 计算当前窗口的长度
                   int currentLength = right - left + 1;
                   // 更新最大长度
                   maxLength = Math.max(maxLength, currentLength);
                   
                   // 移动右指针
                   right++;
               }
           }
       }
       
       return maxLength;
   }

复杂度分析

时间复杂度分析

  1. 统计字符种类

    • 遍历字符串一次,时间复杂度为 O(n),其中 n 是字符串的长度。
  2. 两两组合

    • 假设字符串中有 m 种不同的字符,那么两两组合的数量是 C(m, 2),即 m * (m - 1) / 2
    • 对于每种组合,我们使用滑动窗口来处理字符串,时间复杂度为 O(n)
    • 因此,两两组合的总时间复杂度为 O(m^2 * n)
  3. 滑动窗口

    • 对于每种组合,滑动窗口的时间复杂度为 O(n)
    • 由于我们有两两组合的数量是 m * (m - 1) / 2,所以滑动窗口的总时间复杂度为 O(m^2 * n)

空间复杂度分析

  1. 统计字符种类

    • 使用 HashSet 存储字符种类,空间复杂度为 O(m),其中 m 是字符种类的数量。
  2. 滑动窗口

    • 使用 HashMap 存储字符的出现次数,空间复杂度为 O(1),因为我们最多只存储两种字符的出现次数。

总结

  • 时间复杂度O(m^2 * n),其中 m 是字符种类的数量,n 是字符串的长度。
  • 空间复杂度O(m),其中 m 是字符种类的数量。

时间复杂度优化

我们可以考虑减少不必要的计算。当前的算法在两两组合时,会对每种组合都进行一次滑动窗口操作,这会导致时间复杂度为 O(m^2 * n),其中 m 是字符种类的数量,n 是字符串的长度。

优化思路

  1. 减少组合数量

    • 我们可以通过减少组合的数量来优化时间复杂度。具体来说,我们可以只考虑那些在字符串中出现频率较高的字符组合。
  2. 优化滑动窗口

    • 在滑动窗口过程中,我们可以通过记录每个字符的出现次数来减少不必要的计算。

优化后的算法步骤

  1. 统计字符频率

    • 遍历字符串,统计每个字符的出现频率。
  2. 选取高频字符

    • 根据字符频率,选取出现频率最高的两个字符进行组合。
  3. 滑动窗口

    • 对于选取的两个字符,使用滑动窗口来找到满足条件的最长子串。
public static int solution(int n, int k, String inp) {
       // 统计字符频率
       Map<Character, Integer> charFrequency = new HashMap<>();
       for (char c : inp.toCharArray()) {
           charFrequency.put(c, charFrequency.getOrDefault(c, 0) + 1);
       }
       
       // 初始化最大长度
       int maxLength = 0;
       // 排除特殊情况
       if (charFrequency.size()<=1) {
        return 1;
       }
       
       // 选取高频字符
       Character[] chars = charFrequency.keySet().toArray(new Character[0]);
       for (int i = 0; i < chars.length; i++) {
           for (int j = i + 1; j < chars.length; j++) {
               char char1 = chars[i];
               char char2 = chars[j];
               
               // 使用滑动窗口
               int left = 0;
               int right = 0;
               int count1 = 0;
               int count2 = 0;
               int modifications = 0;
               
               while (right < n) {
                   char rightChar = inp.charAt(right);
                   
                   if (rightChar == char1) {
                       count1++;
                   } else if (rightChar == char2) {
                       count2++;
                   } else {
                       // 如果不是这两个字符之一,则需要修改
                       modifications++;
                   }
                   
                   // 检查修改次数是否超过k
                   while (modifications > k) {
                       char leftChar = inp.charAt(left);
                       if (leftChar == char1) {
                           count1--;
                       } else if (leftChar == char2) {
                           count2--;
                       } else {
                           modifications--;
                       }
                       left++;
                   }
                   
                   // 计算当前窗口的长度
                   int currentLength = right - left + 1;
                   // 更新最大长度
                   maxLength = Math.max(maxLength, currentLength);
                   
                   // 移动右指针
                   right++;
               }
           }
       }
       
       return maxLength;
   }

关键步骤解释

  1. 统计字符频率

    • 使用 HashMap 统计字符串中每个字符的出现频率。
  2. 选取高频字符

    • 从统计的字符频率中选取出现频率最高的两个字符进行组合。
  3. 滑动窗口:对于选取的两个字符,使用滑动窗口来找到满足条件的最长子串。

    • 扩展窗口:不断移动 right 指针,扩展窗口,直到窗口内的字符种类超过两种。
    • 收缩窗口:当窗口内的字符种类超过两种时,移动 left 指针,收缩窗口,直到窗口内的字符种类再次满足条件。
    • 记录最大长度:在每次扩展和收缩窗口时,记录当前窗口的长度,并更新最大长度。

优化后的时间复杂度

  • 时间复杂度O(m * n),其中 m 是字符种类的数量,n 是字符串的长度。
  • 空间复杂度O(m),其中 m 是字符种类的数量。

通过这种方式,我们可以有效地减少不必要的计算,从而优化时间复杂度。