CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
数据结构与算法入门/课时/经典技巧
RawBlame
课时目录12 节

数据结构与算法入门

  1. 01语言与基础
  2. 02基础数据结构
  3. 03复杂度分析
  4. 04排序算法
  5. 05搜索算法
  6. 06树结构
  7. 07图算法
  8. 08高级结构
  9. 09经典技巧
  10. 10刷题模式
  11. 11练习平台
  12. 12综合练习

经典技巧

DP、贪心、回溯。

30 分钟

算法技巧:双指针、滑动窗口与动态规划入门

这一课你将能做到

  1. 用双指针解决「两数之和」「回文判断」等问题
  2. 用滑动窗口解决「最长无重复子串」「最小覆盖子串」等问题
  3. 用动态规划解决「爬楼梯」「最长递增子序列」等入门问题

为什么要学

技巧是算法的「套路」。掌握双指针、滑动窗口、DP 三大套路,80% 的中等题都有思路。不会技巧,遇到新题就只能干瞪眼。

人话:双指针是「两个人从两端往中间走」;滑动窗口是「可伸缩的放大镜」;动态规划是「把大问题拆成小问题,记下来不重复算」。

先建立图像

双指针(对撞):
[1, 2, 3, 4, 5, 6]  target=7
 ↑                 ↑
 left             right
 1+6=7 ✓

滑动窗口:
[a, b, c, a, b, c, b, b]
    [窗口]
       [窗口]
         [窗口]     ← 维持无重复

动态规划:
爬楼梯: f(n) = f(n-1) + f(n-2)
        1, 2, 3, 5, 8, 13...

跟着做

步骤 1:双指针——两数之和(有序数组)

public class Main {
    public static int[] twoSum(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum == target) return new int[]{left, right};
            if (sum < target) left++;
            else right--;
        }
        return new int[]{-1, -1};
    }
    
    public static void main(String[] args) {
        int[] nums = {2, 7, 11, 15};
        int[] res = twoSum(nums, 9);
        System.out.println(res[0] + ", " + res[1]); // 0, 1
    }
}

O(n),前提是数组已排序。未排序用哈希表。

步骤 2:双指针——判断回文

public class Main {
    public static boolean isPalindrome(String s) {
        int left = 0, right = s.length() - 1;
        while (left < right) {
            while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
            while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
            if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
    
    public static void main(String[] args) {
        System.out.println(isPalindrome("A man, a plan, a canal: Panama")); // true
    }
}

步骤 3:滑动窗口——最长无重复子串

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> lastIndex = new HashMap<>();
        int left = 0, maxLen = 0;
        for (int right = 0; right < s.length(); right++) {
            char c = s.charAt(right);
            if (lastIndex.containsKey(c) && lastIndex.get(c) >= left) {
                left = lastIndex.get(c) + 1; // 窗口左边界右移
            }
            lastIndex.put(c, right);
            maxLen = Math.max(maxLen, right - left + 1);
        }
        return maxLen;
    }
    
    public static void main(String[] args) {
        System.out.println(lengthOfLongestSubstring("abcabcbb")); // 3
        System.out.println(lengthOfLongestSubstring("bbbbb"));    // 1
        System.out.println(lengthOfLongestSubstring("pwwkew"));   // 3
    }
}

O(n),每个字符最多访问两次。

步骤 4:动态规划——爬楼梯

public class Main {
    public static int climbStairs(int n) {
        if (n <= 2) return n;
        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
    
    // 空间优化 O(1)
    public static int climbStairsOpt(int n) {
        if (n <= 2) return n;
        int a = 1, b = 2;
        for (int i = 3; i <= n; i++) {
            int c = a + b;
            a = b;
            b = c;
        }
        return b;
    }
    
    public static void main(String[] args) {
        System.out.println(climbStairs(10));    // 89
        System.out.println(climbStairsOpt(10)); // 89
    }
}

步骤 5:动态规划——最长递增子序列

public class Main {
    public static int lengthOfLIS(int[] nums) {
        if (nums.length == 0) return 0;
        int[] dp = new int[nums.length];
        int maxLen = 1;
        for (int i = 0; i < nums.length; i++) {
            dp[i] = 1; // 每个元素自己就是长度为1的递增子序列
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            maxLen = Math.max(maxLen, dp[i]);
        }
        return maxLen;
    }
    
    public static void main(String[] args) {
        int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
        System.out.println(lengthOfLIS(nums)); // 4 (2,3,7,101)
    }
}

O(n²),可用二分优化到 O(n log n)。

讲清楚要点

  1. 双指针分类:对撞指针(两端向中间)、快慢指针(一快一慢,如检测环)、同向指针(滑动窗口)。
  2. 滑动窗口核心:右指针扩张,左指针收缩,维护窗口内性质。关键是确定「何时收缩」。
  3. DP 三要素:状态定义(dp[i] 表示什么)、状态转移方程(dp[i] 怎么从前面推出来)、初始条件(dp[0] 是多少)。
  4. DP 优化方向:空间压缩(滚动数组)、状态精简、单调队列/二分加速。

你可能会懵的地方

  • 双指针什么时候能用? 数据有序(对撞),或需要满足某种单调性(同向)。无序时先排序,或考虑哈希表。

  • 滑动窗口和双指针有什么区别? 滑动窗口是双指针的特例:左右指针同向移动,维护窗口内性质。双指针范围更广。

  • DP 和贪心怎么选? 贪心每步选局部最优,不回溯;DP 考虑所有可能,记录子问题解。能用贪心解决的问题 DP 也能解,但反之不然。判断标准:是否具有「最优子结构」且「无后效性」。

  • DP 数组开多大? 通常开 n+1 或 n,看状态定义是否包含 0。多维 DP 注意内存,10^8 是上限。

  • 怎么想到 DP 转移方程? 从后往前想:「最后一步是什么?」比如爬楼梯,最后一步要么走 1 阶(从 n-1 来),要么走 2 阶(从 n-2 来)。

错法对照

错法问题正解
双指针忘记移动死循环每轮必须 left++ 或 right--
滑动窗口收缩条件错误结果不对或死循环明确「窗口内不满足什么条件就收缩」
DP 状态定义不清转移方程写不出来明确 dp[i] 的物理意义
DP 初始条件错误边界结果错误手动验证 dp[0]、dp[1]
空间优化维度弄反覆盖未使用的状态倒序遍历或明确新旧状态

卡点急救

  1. 双指针结果不对:打印每轮 left、right、当前值,看是否符合预期。
  2. 滑动窗口死循环:检查右指针是否每轮都移动。for (right++) 是标准写法。
  3. DP 结果不对:打印 dp 数组,手动验证前几个值。或用暴力解法对拍。

面试常问

  • 滑动窗口的模板是什么?

    int left = 0;
    for (int right = 0; right < n; right++) {
        // 扩张:加入 nums[right]
        while (窗口不满足条件) {
            // 收缩:移除 nums[left]
            left++;
        }
        // 更新答案
    }
    
  • DP 和分治的区别? 分治的子问题独立(如归并排序);DP 的子问题重叠(如斐波那契)。DP 用记忆化或递推消除重复计算。

  • 如何判断一个题能不能用 DP? 三要素:最优子结构(最优解包含子问题最优解)、无后效性(未来不受过去影响)、重叠子问题(重复计算)。

自测三问

  1. 双指针中「对撞指针」和「同向指针」分别适合什么场景?
  2. 滑动窗口中,右指针和左指针的移动规则是什么?
  3. 动态规划的三要素是什么?

小结一句话

双指针处理有序或单调问题,滑动窗口维护动态区间,动态规划拆解重叠子问题;掌握三大套路,中等题迎刃而解。

延伸

  • Java 官方文档:数组与集合
  • 下一课:刷题模式总结——常见题型与解题模板。
加载本节练习…
← 上一课高级结构下一课 →刷题模式