青训营刷题算法学习心得 | 豆包MarsCode AI 刷题

150 阅读5分钟

学习方法与心得:滑动窗口学习之旅

在解决豆包 MarsCode AI 刷题题库中的“最长连续子串的最大长度”问题时,我遇到了很大的困难。因为我自认为可以解决该问题的代码频频超时,在自己的ide可以跑通。我尝试过剪枝以缩短遍历范围,但o(2^n)的复杂度果然还是没办法作为本题的解答,在咨询过豆包以后,我学习到了一个新的算法思想——— “滑动窗口”

一、题目解析

/**
 * 小R有一个由小写字母组成的字符串 str1,长度为 n。
 * 字符串中有些位置的字符可以通过修复操作来修改为 a-z 中的任意一个字母,
 * 这个修复操作最多可以进行 m 次。现在,小R想知道,经过最多 m 次修复操作后,
 * 字符串中由相同字符组成的最长连续子串的最大长度是多少。
 * 注意,str2表示哪些下标可以修改,如果为1那么就可以修改,如果为0那么就不行。
 */

解析思路

  1. 我的思路:对于一个字符串str1存在a-z26个不同的字符,其中只有在str2所标注的位置上可以进行更改。那么先使用一个数组,将str2中提示的可以更改的字符数量记录下来,其长度为x,可以用x位向量表示可以被更改的位置,这个向量组中的所有向量有m位可以为1,这样就可以构成解空间。将解空间映射到str2所提示的str1可以更改数据的位置,并将解空间上每个1的位置都分别改成a~z,再逐个检测当前str1最长子串具体是多少。其时间复杂度达到了o(2^n)
  2. 豆包的思路:对于一个字符串,部分字符可以通过修复变为任意字符,修复次数有上限,问经过最多修复后,能得到的由相同字符组成的最长连续子串的最大长度。遍历所有可能的目标字符(如 'a' 到 'z'),对于每个目标字符,计算将字符串修复为该字符时的最大连续子串长度。问题的核心在于如何高效计算这个长度,滑动窗口提供了一种简单有效的方式,通过动态调整窗口边界实现了高效的解法,其时间复杂度仅有o(n)。

二、知识总结

滑动窗口是一种处理数组或字符串问题的高效算法,适用于解决具有连续性质的子序列问题。其基本思路是:

  1. 使用两个指针(通常称为左指针和右指针)构成一个动态的窗口;
  2. 调整窗口的范围,满足条件后记录结果,不满足条件时缩小窗口。

豆包对活动窗口方法给出了以下的关键点:

  • 窗口动态调整:右指针始终向右扩展,而左指针根据条件(如修复次数超限)动态收缩窗口;
  • 状态维护:滑动窗口中的变量(如修复次数)需要通过合理的逻辑更新以维持正确的状态;
  • 时间复杂度优化:通过滑动窗口,仅需线性时间就能完成子问题的求解。

三、学习计划

  1. 分析错题:重点分析错题,记录错误原因,并尝试手写多种解法(如暴力法(一开始我所使用的方法)与滑动窗口法对比)。
  2. 总结规律:在练习后整理常用的滑动窗口题型模板,总结不同问题的滑动窗口调整策略。

四、工具运用

豆包 MarsCode AI 的刷题功能与其他资源的结合帮助我事半功倍:

  1. 结合图解和代码演示:MarsCode 提供的解题图解和代码详解让我直观理解算法的操作过程。
  2. 结合在线资料:当遇到模糊的概念时,我会结合教材或视频网站进行补充学习,例如观看滑动窗口相关视频,进一步巩固知识点。
  3. 即时反馈:MarsCode 提供了交互式测试功能,能够实时验证代码的正确性,并根据反馈调整思路。

附:豆包给出的代码

package JuejinAiCodes;
import java.util.ArrayList;

public class LongestSubString {
    /**
     * 小R有一个由小写字母组成的字符串 str1,长度为 n。
     * 字符串中有些位置的字符可以通过修复操作来修改为 a-z 中的任意一个字母,
     * 这个修复操作最多可以进行 m 次。现在,小R想知道,经过最多 m 次修复操作后,
     * 字符串中由相同字符组成的最长连续子串的最大长度是多少。
     * 注意,str2表示哪些下标可以修改,如果为1那么就可以修改,如果为0那么就不行。
     */

    public static int solution(int n, int m, String str1, String str2) {
        int maxLen = 0;

        // 遍历所有可能的目标字符 'a' 到 'z'
        for (char target = 'a'; target <= 'z'; target++) {
            maxLen = Math.max(maxLen, findMaxLength(n, m, str1, str2, target));
        }

        return maxLen;
    }

    /**
     * 寻找将字符串修复为目标字符 target 时的最长连续子串长度
     */
    public static int findMaxLength(int n, int m, String str1, String str2, char target) {
        int left = 0, right = 0; // 滑动窗口的左右边界
        int changes = 0; // 当前窗口内已经修复的字符数量
        int maxLen = 0;

        while (right < n) {
            // 如果当前字符不是目标字符,且可以修复
            if (str1.charAt(right) != target) {
                if (str2.charAt(right) == '1') {
                    changes++;
                } else {
                    // 当前字符无法修复且不等于目标字符,窗口需要右移
                    left = right + 1;
                    changes = 0;
                }
            }

            // 如果修复操作超出了限制,则缩小窗口
            while (changes > m) {
                if (str1.charAt(left) != target && str2.charAt(left) == '1') {
                    changes--;
                }
                left++;
            }

            // 计算当前窗口的长度
            maxLen = Math.max(maxLen, right - left + 1);
            right++;
        }

        return maxLen;
    }

    public static void main(String[] args) {
        // Add your test cases here
        System.out.println(solution(5, 2, "abcda", "01110") == 3);
        System.out.println(solution(7, 2, "abbaccb", "1001001") == 4);
        System.out.println(solution(3, 0, "aab", "101") == 2);
        System.out.println(solution(5, 1, "aaaaa", "00000") == 5);
    }
}