【C++】面试:数据结构与算法

C++ 面试必考模块,笔试手撕、算法题、现场coding核心考点,链表/树/排序/查找为高频题型,标准答案直接背诵,代码模板可直接默写。


1. 链表(单链/双链)/ 循环链表

1.1 链表核心特性

  • 单链表:节点含数据 + next 指针,内存离散,通过指针串联。
  • 双链表:节点含 prev + next,支持双向遍历。
  • 循环链表:尾节点 next 指向头节点,形成环。

1.2 链表 vs 数组

特性 链表 数组
存储 内存离散 内存连续
访问 O(n) 顺序遍历 O(1) 随机访问
增删 O(1) 改指针 O(n) 元素搬迁
内存 额外存指针 无额外开销

1.3 核心操作(手撕模板)

  • 反转链表:prev=null, curr=head, while(curr) { next=curr->next; curr->next=prev; prev=curr; curr=next; }
  • 链表环检测:快慢指针法 (fast=2*slow)
  • 链表倒数第K个:双指针 (p1先走k步,p2同步)

1.4 高频面试题

  1. 反转链表:迭代/递归
  2. 链表环入口:快慢指针数学推导
  3. 合并两个有序链表:虚拟头节点
  4. 链表相交:双指针法
  5. 删除倒数第N个:双指针一次遍历

2. 二叉树(遍历 / 重建 / 高度)

2.1 二叉树遍历方式

  • 前序:根-左-右
  • 中序:左-根-右(排序树)
  • 后序:左-右-根
  • 层序:队列 + BFS

2.2 递归遍历模板

cpp 复制代码
void traversal(Node* root) {
    if (!root) return;
    // 前序: visit(root)
    traversal(root->left);
    // 中序: visit(root)
    traversal(root->right);
    // 后序: visit(root)
}

2.3 核心算法

  • 二叉树高度 :递归 height = max(height(left), height(right)) + 1
  • 镜像翻转:递归交换左右子树
  • 最近公共祖先:递归查找两节点,路径分叉处
  • 重建二叉树:前序+中序 / 后序+中序

2.4 高频面试题

  1. 二叉树深度:递归
  2. 对称二叉树:递归比较
  3. 二叉树镜像:递归翻转
  4. 路径总和:回溯
  5. 层序遍历:队列实现

2.5 二叉搜索树 (BST)

  • 左子树值 < 根值 < 右子树值
  • 中序遍历得到有序序列
  • 查找/插入/删除 O(log n)

3. 排序算法(八大排序)

3.1 时间复杂度汇总

排序 平均 最坏 稳定 场景
冒泡 O(n²) O(n²) 稳定 教学
简单选择 O(n²) O(n²) 不稳定 教学
简单插入 O(n²) O(n²) 稳定 基本有序
希尔 O(n^1.5) O(n²) 不稳定 进阶
堆排序 O(nlogn) O(nlogn) 不稳定 Top-K
归并 O(nlogn) O(nlogn) 稳定 外排序
快速 O(nlogn) O(n²) 不稳定 最常用
计数 O(n+k) O(n+k) 稳定 小范围整数

3.2 快排模板(手撕必背)

cpp 复制代码
void quickSort(vector<int>& arr, int left, int right) {
    if (left >= right) return;
    int pivot = partition(arr, left, right);
    quickSort(arr, left, pivot - 1);
    quickSort(arr, pivot + 1, right);
}
int partition(vector<int>& arr, int left, int right) {
    int p = arr[left];  // 基准
    while (left < right) {
        while (left < right && arr[right] >= p) right--;
        arr[left] = arr[right];
        while (left < right && arr[left] <= p) left++;
        arr[right] = arr[left];
    }
    arr[left] = p;
    return left;
}

3.3 归并排序(分治)

  • 分解:递归二分数组
  • 合并:双指针有序合并
  • 稳定排序,适合外排序

3.4 堆排序

  • 建大顶堆:父节点 >= 左右子节点
  • 排序:堆顶与末尾交换,调节堆
  • Top-K 问题最优解

4. 查找算法(二分/哈希)

4.1 二分查找

  • 前提:有序数组
  • 模板
cpp 复制代码
int binarySearch(vector<int>& arr, int target) {
    int left = 0, right = arr.size() - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) return mid;
        else if (arr[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}
  • 边界:left < right vs left <= right
  • 变形:查找左边界、查找右边界、旋转数组

4.2 二分答案

  • 有单调性 monotone Function 可用二分
  • 求最小值/最大值/满足条件的最优解

4.3 哈希查找

  • unordered_map O(1) 平均
  • 哈希冲突:链地址法
  • 再哈希:扩容时重新计算

4.4 面试追问

  • 为什么二分用 left + (right-left)/2? 防溢出
  • 二分查找 vs 二叉搜索树? 均logN,树更均衡

5. 位运算(常用技巧)

5.1 常用公式

  • n & (n-1):消除最低位的1
  • n & (-n):取最低位的1
  • n ^ n = 0:消元
  • n | 0 = n
  • ~n + 1 = -n

5.2 代码模板

cpp 复制代码
// 判断奇偶
if (n & 1) // n是奇数

// 消除最低位的1
n &= (n-1)

// 获取最低位的1
int lowestBit = n & (-n)

// 判断是否为2的幂
n > 0 && (n & (n-1)) == 0

5.3 应用场景

  • 统计1的个数(布隆过滤器)
  • 交换两数(不加临时变量)
  • 找出缺失的整数

6. 栈与队列(应用/设计)

6.1 栈应用

  • 函数调用栈:递归实现
  • 括号匹配:栈顶匹配
  • 前缀/中缀/后缀表达式:运算符优先级
  • 单调栈:求下一个更大元素

6.2 队列应用

  • BFS 层序遍历
  • 滑动窗口最大值:双端队列
  • 生产者-消费者

6.3 队设计

  • 两个栈实现队列
  • 两个队列实现栈

6.4 模板

cpp 复制代码
// 单调栈 - 下一个更大元素
vector<int> nextGreater(vector<int>& nums) {
    vector<int> res(nums.size(), -1);
    stack<int> st;
    for (int i = 0; i < nums.size(); i++) {
        while (!st.empty() && nums[i] > nums[st.top()]) {
            res[st.top()] = nums[i];
            st.pop();
        }
        st.push(i);
    }
    return res;
}

7. Top-K 问题

7.1 解法对比

方法 时间 空间 特点
排序 O(nlog n) O(1) 简单粗暴
O(nlog k) O(k) 海量数据
快速选择 O(n) O(1) 平均最快
计数排序 O(n+k) O(k) 小范围整数

7.2 堆模板(手撕)

cpp 复制代码
// 小顶堆 Top-K
priority_queue<int, vector<int>, greater<int>> pq;
for (int x : nums) {
    pq.push(x);
    if (pq.size() > k) pq.pop();
}

7.3 TOP-K 变形

  • K个最小/最大
  • 中位数(第k大/小)
  • 频率最高的K个

8. 回溯与 DFS/BFS

8.1 DFS 模板

cpp 复制代码
void dfs(Node* node) {
    if (visited[node]) return;
    visited[node] = true;
    // visit(node)
    for (auto next : node.neighbors) {
        if (!visited[next]) dfs(next);
    }
}

8.2 回溯模板

cpp 复制代码
void backtrack(vector<int>& path, ...) {
    if (满足条件) {
        result.push_back(path);
        return;
    }
    for (选择 : candidates) {
        path.push_back(选择);
        backtrack(path, ...);
        path.pop_back();  // 回溯
    }
}

8.3 经典问题

  • 全排列
  • 子集/组合
  • N皇后
  • 岛屿数量
  • 路径总和

8.4 BFS 模板

cpp 复制代码
queue<Node*> q;
q.push(start);
while (!q.empty()) {
    Node* cur = q.front(); q.pop();
    for (auto next : cur.neighbors) {
        if (!visited[next]) {
            visited[next] = true;
            q.push(next);
        }
    }
}

9. 动态规划(DP)

9.1 DP 四步曲

  1. 定义状态:dpi 含义
  2. 状态转移:dpi = f(dpi-1)
  3. 初始化:dp0、dp1
  4. 遍历顺序:从前向后

9.2 经典题目模板

cpp 复制代码
// 斐波那契
dp[0]=0, dp[1]=1;
for (int i=2; i<=n; i++)
    dp[i] = dp[i-1] + dp[i-2];

// 最长上升子序列
for (int i=1; i<n; i++)
    for (int j=0; j<i; j++)
        if (nums[i] > nums[j])
            dp[i] = max(dp[i], dp[j]+1);

// 背包问题
for (int i=0; i<n; i++)
    for (int j=W; j>=w[i]; j--)
        dp[j] = max(dp[j], dp[j-w[i]]+v[i]);

9.3 经典问题

  • 斐波那契数列
  • 爬楼梯
  • 打家劫舍
  • 最长上升子序列 LCS
  • 编辑距离
  • 背包问题

10. 双指针与滑动窗口

10.1 双指针

  • 对撞指针:左右逼近,有序数组
  • 快慢指针:链表判环、删除重复

10.2 滑动窗口

cpp 复制代码
int slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    while (right < s.size()) {
        char c = s[right++];
        // 扩大窗口
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c]) valid++;
        }

        // 收缩窗口
        while (valid == need.size()) {
            // 更新答案
            char d = s[left++];
            if (need.count(d)) {
                if (window[d] == need[d]) valid--;
                window[d]--;
            }
        }
    }
}

10.3 应用

  • 有序数组两数之和
  • 移除重复元素
  • 最大不重复子串长度
  • 最短覆盖子串

🔥 本章综合高频追问

  1. :快排为什么不稳定?

    :基准选择可能导致相同元素顺序变化。

  2. :为什么哈希表不稳定?

    :哈希碰撞,红黑树保证O(logN)。

  3. :DFS vs BFS 如何选?

    :找最短路径用BFS,其他可用DFS。

  4. :动态规划如何确定状态?

    :最后一步/最值问题/是否存在。


📝 模块总结

本模块覆盖 C++ 面试手撕代码核心题型,链表、二叉树、排序、查找、DP 为必考内容。全部理解并能快速默写,可应对90%算法 coding 面。