Skip to content

算法面试题型与解题思路

更新: 5/15/2026 字数: 0 字 时长: 0 分钟

社招面试中算法题以 LeetCode Medium 为主,偶尔 Easy/Hard。重点不是刷题量,而是掌握每种题型的核心思路和模板。


1. 数组与双指针

核心思路

  • 左右指针:从两端向中间逼近,常用于有序数组、容器盛水类问题
  • 快慢指针:一快一慢,用于链表环检测、删除倒数第 N 个节点
  • 滑动窗口:维护一个窗口 [left, right),right 扩张、left 收缩,用于子串/子数组问题

滑动窗口模板

java
int left = 0;
Map<Character, Integer> window = new HashMap<>();

for (int right = 0; right < s.length(); right++) {
    char c = s.charAt(right);
    window.merge(c, 1, Integer::sum); // 扩张:加入右边元素

    while (窗口需要收缩) {
        char d = s.charAt(left);
        window.merge(d, -1, Integer::sum); // 收缩:移除左边元素
        left++;
    }
    // 在这里更新结果
}

经典题目

题目思路难度
两数之和HashMap 存已遍历的值Easy
三数之和排序 + 固定一个数 + 左右指针Medium
盛最多水的容器左右指针,移动较短的一边Medium
无重复字符的最长子串滑动窗口 + HashSetMedium
最小覆盖子串滑动窗口 + 计数器Hard
合并区间按起点排序,逐个合并Medium
下一个排列从右找第一个降序位,交换后翻转Medium

2. 链表

核心思路

  • 虚拟头节点(dummy):简化头节点的边界处理
  • 快慢指针:找中点、判环、找环入口
  • 反转链表:迭代(prev/curr/next 三指针)或递归
  • 合并链表:归并思想

反转链表模板

java
ListNode prev = null, curr = head;
while (curr != null) {
    ListNode next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
}
return prev;

经典题目

题目思路难度
反转链表三指针迭代Easy
反转链表 II(区间反转)找到区间,断开反转再接回Medium
合并两个有序链表dummy + 逐个比较Easy
合并 K 个有序链表小顶堆 / 分治归并Hard
环形链表 II快慢指针相遇后,头和相遇点同速走Medium
LRU 缓存HashMap + 双向链表Medium
相交链表两指针各走一遍对方的路Easy
K 个一组翻转链表分组 + 组内反转 + 拼接Hard

3. 哈希表

核心思路

  • 用空间换时间,O(1) 查找
  • 计数:统计频率、判断异位词
  • 映射:两数之和、字符对应关系
  • 去重:判断是否出现过

经典题目

题目思路难度
两数之和遍历时查 HashMapEasy
字母异位词分组排序后的字符串作为 keyMedium
最长连续序列HashSet + 只从起点开始计数Medium
前 K 个高频元素HashMap 计数 + 小顶堆/桶排序Medium

4. 栈与队列

核心思路

  • 单调栈:维护一个单调递增/递减的栈,用于"下一个更大/更小元素"类问题
  • 辅助栈:括号匹配、表达式求值、最小栈
  • 栈模拟:DFS、路径简化

单调栈模板(下一个更大元素)

java
int[] result = new int[nums.length];
Deque<Integer> stack = new ArrayDeque<>(); // 存下标

for (int i = nums.length - 1; i >= 0; i--) {
    while (!stack.isEmpty() && nums[stack.peek()] <= nums[i]) {
        stack.pop(); // 弹出比当前小的
    }
    result[i] = stack.isEmpty() ? -1 : nums[stack.peek()];
    stack.push(i);
}

经典题目

题目思路难度
有效的括号栈匹配Easy
每日温度单调递减栈Medium
柱状图中最大的矩形单调递增栈,找左右边界Hard
最小栈辅助栈同步记录最小值Medium
接雨水单调栈 / 双指针 / 动态规划Hard
字符串解码双栈(数字栈 + 字符串栈)Medium

5. 二叉树

核心思路

  • 递归三要素:①返回值和参数 ②终止条件 ③单层逻辑
  • 遍历方式:前序(根左右)、中序(左根右)、后序(左右根)、层序(BFS)
  • 分治思想:把问题拆成左子树和右子树分别解决,再合并结果

递归模板

java
// 自顶向下(传参数下去)
void traverse(TreeNode node, int depth) {
    if (node == null) return;
    // 前序位置:处理当前节点
    traverse(node.left, depth + 1);
    // 中序位置
    traverse(node.right, depth + 1);
    // 后序位置
}

// 自底向上(收集返回值)
int maxDepth(TreeNode node) {
    if (node == null) return 0;
    int left = maxDepth(node.left);
    int right = maxDepth(node.right);
    return Math.max(left, right) + 1; // 后序位置合并结果
}

经典题目

题目思路难度
二叉树的最大深度递归:max(左, 右) + 1Easy
翻转二叉树递归交换左右子树Easy
对称二叉树双指针递归比较Easy
二叉树的层序遍历BFS + 队列Medium
从前序与中序构造二叉树前序第一个是根,中序分左右Medium
二叉树的最近公共祖先后序遍历,左右子树分别找Medium
二叉树的直径后序遍历,每个节点算左+右深度Easy
验证二叉搜索树中序遍历递增 / 递归传上下界Medium
二叉树的序列化与反序列化前序遍历 + null 标记Hard
二叉树中的最大路径和后序遍历,维护全局最大值Hard

6. 二叉搜索树(BST)

核心性质

  • 左子树所有节点 < 根 < 右子树所有节点
  • 中序遍历是有序的(这是解题关键)

经典题目

题目思路难度
验证二叉搜索树中序遍历递增 / 递归传范围Medium
二叉搜索树中第 K 小的元素中序遍历到第 K 个Medium
将有序数组转换为 BST取中间元素为根,递归建树Easy
删除 BST 中的节点找后继节点替换Medium

7. 回溯(Backtracking)

核心思路

回溯 = DFS + 撤销选择。本质是在决策树上穷举所有路径。

做选择 → 递归进入下一层 → 撤销选择

回溯模板

java
List<List<Integer>> result = new ArrayList<>();

void backtrack(int[] nums, List<Integer> path, boolean[] used) {
    if (满足结束条件) {
        result.add(new ArrayList<>(path)); // 注意拷贝
        return;
    }
    for (int i = 0; i < nums.length; i++) {
        if (used[i]) continue;          // 剪枝
        path.add(nums[i]);              // 做选择
        used[i] = true;
        backtrack(nums, path, used);    // 递归
        path.remove(path.size() - 1);   // 撤销选择
        used[i] = false;
    }
}

去重技巧

排列/组合中有重复元素时:先排序,然后 if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue;

经典题目

题目思路难度
全排列回溯 + used 数组Medium
全排列 II(有重复)排序 + 去重剪枝Medium
子集每个元素选或不选Medium
组合总和回溯 + 可重复选(i 不加 1)Medium
组合总和 II(不可重复)排序 + 去重 + i+1Medium
N 皇后回溯 + 列/对角线冲突检测Hard
电话号码的字母组合回溯,每层对应一个数字的字母Medium
括号生成回溯,左括号数 ≥ 右括号数Medium
单词搜索二维网格 DFS 回溯Medium

8. 动态规划(DP)

核心思路

  1. 定义状态:dp[i] 或 dp[i][j] 代表什么
  2. 状态转移方程:dp[i] 怎么从之前的状态推导出来
  3. 初始条件:dp[0] 或 dp[0][0] 是什么
  4. 遍历顺序:确保计算 dp[i] 时所依赖的状态已经算好

常见 DP 类型

8.1 线性 DP

dp[i] = f(dp[i-1], dp[i-2], ...)
题目状态定义转移方程难度
爬楼梯dp[i] = 到第 i 阶的方法数dp[i] = dp[i-1] + dp[i-2]Easy
打家劫舍dp[i] = 前 i 家最大金额dp[i] = max(dp[i-1], dp[i-2]+nums[i])Medium
最长递增子序列dp[i] = 以 i 结尾的 LIS 长度dp[i] = max(dp[j]+1),j < i 且 nums[j] < nums[i]Medium
最大子数组和dp[i] = 以 i 结尾的最大和dp[i] = max(nums[i], dp[i-1]+nums[i])Medium
解码方法dp[i] = 前 i 个字符的解码数dp[i] = dpi-1 + dpi-2Medium

8.2 二维 DP / 网格 DP

dp[i][j] = f(dp[i-1][j], dp[i][j-1], ...)
题目状态定义转移方程难度
不同路径dp[i][j] = 到 (i,j) 的路径数dp[i][j] = dp[i-1][j] + dp[i][j-1]Medium
最小路径和dp[i][j] = 到 (i,j) 的最小和dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]Medium
编辑距离dp[i][j] = word1 前 i 个变成 word2 前 j 个的最少操作相等跳过,不等取 min(增删改)+1Hard
最长公共子序列dp[i][j] = 前 i 和前 j 的 LCS 长度相等 dp[i-1][j-1]+1,不等 max(dp[i-1][j], dp[i][j-1])Medium

8.3 背包 DP

0-1 背包:每个物品只能选一次
完全背包:每个物品可以选无限次
java
// 0-1 背包(一维优化,逆序遍历)
for (int i = 0; i < n; i++) {
    for (int j = W; j >= weight[i]; j--) {
        dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

// 完全背包(正序遍历)
for (int i = 0; i < n; i++) {
    for (int j = weight[i]; j <= W; j++) {
        dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
    }
}
题目背包类型难度
分割等和子集0-1 背包(目标 sum/2)Medium
零钱兑换完全背包(求最少硬币数)Medium
零钱兑换 II完全背包(求组合数)Medium
目标和0-1 背包(转化为子集和问题)Medium
单词拆分完全背包变体Medium

8.4 区间 DP

dp[i][j] = 区间 [i, j] 上的最优解
枚举分割点 k:dp[i][j] = f(dp[i][k], dp[k+1][j])
题目思路难度
最长回文子串dp[i][j] = s[i..j] 是否回文Medium
戳气球区间 DP,枚举最后戳的气球Hard

9. 贪心

核心思路

每一步都做局部最优选择,期望得到全局最优。关键是证明贪心策略的正确性。

经典题目

题目贪心策略难度
买卖股票的最佳时机 II只要明天涨就今天买Medium
跳跃游戏维护能到达的最远位置Medium
跳跃游戏 II每步跳到能到最远的位置Medium
分发糖果左右各扫一遍取 maxHard
无重叠区间按结束时间排序,贪心选不重叠的Medium
划分字母区间记录每个字母最后出现位置Medium

10. BFS / DFS(图与网格搜索)

BFS 模板(层序遍历 / 最短路径)

java
Queue<int[]> queue = new LinkedList<>();
boolean[][] visited = new boolean[m][n];
queue.offer(new int[]{startX, startY});
visited[startX][startY] = true;
int step = 0;

while (!queue.isEmpty()) {
    int size = queue.size();
    for (int i = 0; i < size; i++) {
        int[] curr = queue.poll();
        if (curr 是目标) return step;

        for (int[] dir : directions) { // {{0,1},{0,-1},{1,0},{-1,0}}
            int nx = curr[0] + dir[0], ny = curr[1] + dir[1];
            if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {
                visited[nx][ny] = true;
                queue.offer(new int[]{nx, ny});
            }
        }
    }
    step++;
}

经典题目

题目思路难度
岛屿数量DFS/BFS 遍历连通区域Medium
岛屿的最大面积DFS 计数Medium
腐烂的橘子多源 BFSMedium
课程表(拓扑排序)BFS + 入度表Medium
课程表 II拓扑排序输出顺序Medium
单词接龙BFS 最短路径Hard

11. 二分查找

核心思路

有序 + 查找 → 二分。关键是确定搜索区间和边界条件。

三种模板

java
// 1. 标准二分:找 target
int left = 0, right = nums.length - 1;
while (left <= right) {
    int mid = left + (right - left) / 2;
    if (nums[mid] == target) return mid;
    else if (nums[mid] < target) left = mid + 1;
    else right = mid - 1;
}

// 2. 左边界:找第一个 >= target 的位置
int left = 0, right = nums.length - 1;

while (left <= right) {
    int mid = left + (right - left) / 2;
    if (nums[mid] < target) {
        left = mid + 1;   // 不够大,往右走
    } else {
        right = mid - 1;  // 太大或相等,往左找更早的
    }
}

return left; // 最终 left 就是第一个 ≥ target 的下标

// 3. 右边界:找最后一个 <= target 的位置
int left = 0, right = nums.length - 1;

while (left <= right) {
    int mid = left + (right - left) / 2;
    if (nums[mid] > target) {
        right = mid - 1;  // 太大,往左走
    } else {
        left = mid + 1;   // 太小或相等,往右找更晚的
    }
}

return right; // 最终 right 就是最后一个 ≤ target 的下标

经典题目

题目思路难度
搜索旋转排序数组二分,判断哪半边有序Medium
在排序数组中查找元素的第一个和最后一个位置两次二分找左右边界Medium
搜索二维矩阵展开为一维二分 / 右上角搜索Medium
寻找旋转排序数组中的最小值二分,和右端点比较Medium
寻找两个正序数组的中位数二分找分割线Hard

12. 堆(优先队列)

核心思路

  • Top K 问题:用大小为 K 的小顶堆
  • 合并 K 个有序序列:小顶堆维护每个序列的当前最小值
  • 动态中位数:大顶堆(左半)+ 小顶堆(右半)

经典题目

题目思路难度
前 K 个高频元素HashMap 计数 + 小顶堆Medium
数组中的第 K 个最大元素小顶堆 / 快速选择Medium
合并 K 个升序链表小顶堆Hard
数据流的中位数大顶堆 + 小顶堆Hard

13. 前缀和 / 差分

前缀和

java
// 构建前缀和
int[] prefix = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
    prefix[i + 1] = prefix[i] + nums[i];
}
// 区间和 [i, j] = prefix[j+1] - prefix[i]

经典题目

题目思路难度
和为 K 的子数组前缀和 + HashMapMedium
除自身以外数组的乘积左前缀积 × 右前缀积Medium

14. 并查集(Union-Find)

模板

java
class UnionFind {
    int[] parent, rank;

    UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) parent[i] = i;
    }

    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]); // 路径压缩
        return parent[x];
    }

    void union(int x, int y) {
        int px = find(x), py = find(y);
        if (px == py) return;
        if (rank[px] < rank[py]) { int t = px; px = py; py = t; }
        parent[py] = px; // 按秩合并
        if (rank[px] == rank[py]) rank[px]++;
    }
}

经典题目

题目思路难度
岛屿数量(并查集解法)合并相邻陆地Medium
冗余连接加边时发现环Medium

15. 字符串

经典题目

题目思路难度
最长回文子串中心扩展 / DPMedium
最长公共前缀纵向比较Easy
字符串转换整数 (atoi)状态机 / 逐字符处理Medium
实现 strStr()KMP / 直接匹配Easy

16. 位运算

常用技巧

n & (n - 1)    → 消除最低位的 1
n & (-n)       → 获取最低位的 1
a ^ a = 0      → 异或找唯一数

经典题目

题目思路难度
只出现一次的数字全部异或Easy
位 1 的个数n & (n-1) 循环计数Easy
2 的幂n > 0 && (n & (n-1)) == 0Easy

17. 设计题

题目核心数据结构难度
LRU 缓存HashMap + 双向链表Medium
LFU 缓存HashMap + 频率桶(双向链表)Hard
最小栈辅助栈Medium
用栈实现队列两个栈Easy

面试高频 Top 30(必刷)

按出现频率排序,这些题在社招面试中反复出现:

  1. 两数之和
  2. 反转链表
  3. LRU 缓存
  4. 无重复字符的最长子串
  5. 合并两个有序链表
  6. 二叉树的层序遍历
  7. 最大子数组和
  8. 三数之和
  9. 买卖股票的最佳时机
  10. 岛屿数量
  11. 全排列
  12. 二叉树的最近公共祖先
  13. 搜索旋转排序数组
  14. 合并区间
  15. 爬楼梯
  16. 零钱兑换
  17. 有效的括号
  18. 接雨水
  19. 最长递增子序列
  20. 二叉树的最大深度
  21. 环形链表 II
  22. 每日温度
  23. 编辑距离
  24. 前 K 个高频元素
  25. 课程表
  26. 最小路径和
  27. 单词搜索
  28. 数组中的第 K 个最大元素
  29. 最长回文子串
  30. K 个一组翻转链表

解题策略

面试中的做题流程

1. 理解题意(2 分钟)
   → 确认输入输出、边界条件、数据规模
   → 用例子验证理解

2. 说思路(3 分钟)
   → 先说暴力解法和复杂度
   → 再说优化思路
   → 和面试官确认方向

3. 写代码(15 分钟)
   → 先写主体逻辑,再处理边界
   → 变量命名清晰
   → 边写边解释

4. 测试(5 分钟)
   → 用示例 dry run 一遍
   → 考虑边界:空输入、单元素、全相同、最大值

看到题目的第一反应

有序数组/矩阵        → 二分查找
最优/最大/最小/最长   → DP 或贪心
所有方案/排列/组合    → 回溯
最短路径/层数        → BFS
连通性              → DFS / 并查集
Top K               → 堆
子串/子数组          → 滑动窗口 / 前缀和
树的问题            → 递归(想清楚用前序/中序/后序)

实习面试刷题重点

实习面试算法通常比社招系统设计更高频,建议先把 Easy 和常见 Medium 刷熟,重点练“边写边讲”和复杂度分析。

必须掌握的题型

题型必刷能力代表题
数组/哈希下标、去重、计数、映射关系两数之和、合并区间、前 K 个高频元素
字符串字符计数、双指针、滑动窗口有效括号、最长无重复子串、最长回文子串
链表指针移动、反转、快慢指针反转链表、合并两个有序链表、环形链表
栈/队列匹配、单调栈、BFS有效括号、每日温度、二叉树层序遍历
二叉树递归、遍历、深度最大深度、层序遍历、最近公共祖先
动态规划状态定义、转移方程爬楼梯、最大子数组和、买卖股票

实习面试答题模板

1. 先复述题意,确认输入输出和边界。
2. 先说最直接的思路,再说优化方式。
3. 写代码时主动解释关键变量含义。
4. 写完用一个普通用例和一个边界用例 dry run。
5. 最后说时间复杂度和空间复杂度。

Android 实习建议优先级

  1. 先刷数组、字符串、链表、栈队列,保证 Easy 稳定 AC。
  2. 再刷二叉树、滑动窗口、二分查找,掌握常见模板。
  3. 最后补基础 DP 和回溯,不要一开始死磕 Hard。
  4. 每道题都要能口述思路,面试时“沉默写代码”会明显扣分。