首页 关于 文章 作品集 工具 联系
返回文章列表

面试高频算法整理:排序、二叉树与动态规划

面试算法题是每个开发者绕不过的坎。不管是校招还是社招,面试官总喜欢扔几道算法题来考察你的思维能力和代码功底。这篇文章整理了面试中出现频率最高的几类题型,用 Dart 和 Python 双语实现,帮助你快速上手。

一、排序算法

排序是最基础的算法考点。面试中最常考的是快速排序和归并排序,要求能手写实现。

1.1 快速排序

// Dart 实现
void quickSort<T extends Comparable>(List<T> arr, int left, int right) {{
  if (left >= right) return;

  int pivotIndex = _partition(arr, left, right);
  quickSort(arr, left, pivotIndex - 1);
  quickSort(arr, pivotIndex + 1, right);
}}

int _partition<T extends Comparable>(List<T> arr, int left, int right) {{
  T pivot = arr[right];
  int i = left;

  for (int j = left; j < right; j++) {{
    if (arr[j].compareTo(pivot) <= 0) {{
      _swap(arr, i, j);
      i++;
    }}
  }}
  _swap(arr, i, right);
  return i;
}}

void _swap<T>(List<T> arr, int i, int j) {{
  T temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}}

// Python 实现
def quick_sort(arr, left, right):
    if left >= right:
        return
    pivot_idx = partition(arr, left, right)
    quick_sort(arr, left, pivot_idx - 1)
    quick_sort(arr, pivot_idx + 1, right)

def partition(arr, left, right):
    pivot = arr[right]
    i = left
    for j in range(left, right):
        if arr[j] <= pivot:
            arr[i], arr[j] = arr[j], arr[i]
            i += 1
    arr[i], arr[right] = arr[right], arr[i]
    return i

1.2 归并排序

// Dart 实现
List<int> mergeSort(List<int> arr) {{
  if (arr.length <= 1) return arr;

  int mid = arr.length ~/ 2;
  List<int> left = mergeSort(arr.sublist(0, mid));
  List<int> right = mergeSort(arr.sublist(mid));

  return _merge(left, right);
}}

List<int> _merge(List<int> left, List<int> right) {{
  List<int> result = [];
  int i = 0, j = 0;

  while (i < left.length && j < right.length) {{
    if (left[i] <= right[j]) {{
      result.add(left[i++]);
    }} else {{
      result.add(right[j++]);
    }}
  }}
  result.addAll(left.sublist(i));
  result.addAll(right.sublist(j));
  return result;
}}

二、二叉树遍历

二叉树是面试的"常青树",几乎每次都有。掌握前中后序遍历和层序遍历是基本要求。

2.1 节点定义

class TreeNode {{
  int val;
  TreeNode? left;
  TreeNode? right;
  TreeNode(this.val, [this.left, this.right]);
}}

2.2 前中后序遍历(递归 + 迭代)

// 前序遍历(递归)
List<int> preorder(TreeNode? root) {{
  if (root == null) return [];
  return [root.val, ...preorder(root.left), ...preorder(root.right)];
}}

// 中序遍历(迭代 - 用栈)
List<int> inorder(TreeNode? root) {{
  List<int> result = [];
  List<TreeNode> stack = [];
  TreeNode? current = root;

  while (current != null || stack.isNotEmpty) {{
    while (current != null) {{
      stack.add(current);
      current = current.left;
    }}
    current = stack.removeLast();
    result.add(current.val);
    current = current.right;
  }}
  return result;
}}

// 层序遍历(BFS)
List<List<int>> levelOrder(TreeNode? root) {{
  if (root == null) return [];

  List<List<int>> result = [];
  List<TreeNode> queue = [root];

  while (queue.isNotEmpty) {{
    int levelSize = queue.length;
    List<int> level = [];

    for (int i = 0; i < levelSize; i++) {{
      TreeNode node = queue.removeAt(0);
      level.add(node.val);
      if (node.left != null) queue.add(node.left!);
      if (node.right != null) queue.add(node.right!);
    }}
    result.add(level);
  }}
  return result;
}}

三、动态规划

动态规划(DP)是面试中区分度最高的题型。核心思路:找到状态定义状态转移方程

3.1 爬楼梯(入门)

// 每次可以爬1或2级台阶,有多少种方法?
int climbStairs(int n) {{
  if (n <= 2) return n;
  int prev1 = 2, prev2 = 1;
  for (int i = 3; i <= n; i++) {{
    int current = prev1 + prev2;
    prev2 = prev1;
    prev1 = current;
  }}
  return prev1;
}}

3.2 0-1 背包问题

// 给定容量 W 和 n 个物品(重量 w[i]、价值 v[i]),求最大价值
int knapsack(int W, List<int> w, List<int> v) {{
  int n = w.length;
  List<List<int>> dp = List.generate(
    n + 1, (_) => List.filled(W + 1, 0),
  );

  for (int i = 1; i <= n; i++) {{
    for (int j = 1; j <= W; j++) {{
      if (w[i - 1] <= j) {{
        dp[i][j] = max(
          dp[i - 1][j],                                    // 不选
          dp[i - 1][j - w[i - 1]] + v[i - 1],           // 选
        );
      }} else {{
        dp[i][j] = dp[i - 1][j];
      }}
    }}
  }}
  return dp[n][W];
}}

3.3 最长递增子序列(LIS)

// O(n log n) 解法:二分查找 + 贪心
int lengthOfLIS(List<int> nums) {{
  List<int> tails = [];

  for (int num in nums) {{
    // 二分查找第一个 >= num 的位置
    int left = 0, right = tails.length;
    while (left < right) {{
      int mid = (left + right) ~/ 2;
      if (tails[mid] < num) {{
        left = mid + 1;
      }} else {{
        right = mid;
      }}
    }}

    if (left == tails.length) {{
      tails.add(num);
    }} else {{
      tails[left] = num;
    }}
  }}
  return tails.length;
}}

四、常用技巧总结

题型 核心思路 时间复杂度
排序分治、双指针O(n log n)
二叉树递归、BFS/DFS、栈O(n)
动态规划状态定义 + 转移方程O(n²) 或更优
双指针滑动窗口、左右夹逼O(n)
哈希表空间换时间O(n)

总结

算法面试的核心不是背题,而是掌握解题思路。遇到题目先想:这是什么类型?用暴力解法能不能做?能优化吗?优化空间在哪?养成这个思考习惯,比刷 1000 道题更有用。

建议每天花 30 分钟写 1-2 道算法题,坚持一个月,你会发现自己进步非常明显。