简介:这份资源是《剑指Offer》C++版配套源代码合集,面向准备技术面试的求职者与希望夯实算法基础的C++学习者,覆盖数组、链表、树、栈与队列等基础数据结构,以及动态规划、递归、字符串匹配、排序搜索、设计模式、STL容器与智能指针、内存管理、面向对象、异常处理和模板泛型编程等高频考点。压缩包共2098个文件,以242个cpp源文件、226个头文件为核心实现,辅以vcproj、vcxproj、sln等工程配置,以及exe、pdb、obj、tlog等编译调试产物,整体约44.2MB,可直接在Visual Studio中打开运行与调试。目前已有373人学习。读者可对照书中题目逐题研读代码实现,理解解题思路与边界处理,并通过动手编译、修改和调试加深对算法与C++语言特性的掌握,适合作为面试刷题与工程实践结合的参考材料。
1. 剑指 Offer C++ 面试考点:从刷题到拿 Offer 的完整路径
很多人刷《剑指 Offer》的方式是打开题库、按顺序做、对答案、下一题。刷完一遍,面试官换个问法就卡壳。问题不在题量,在于没有把每道题背后的考点抽出来。C++ 岗的面试,算法题只是入场券,面试官真正想确认的是:你对指针、内存、STL 容器的理解是否到位,能不能在压力下写出边界正确的代码。这份源代码资源覆盖了书里全部题目的 C++ 实现,但比代码本身更值钱的,是每道题对应的考点归类。适合已经刷过一轮、准备冲刺面试的人,也适合刚开始刷但想一次刷到位的人。下面按考点模块拆开讲,每章都配真题和答题思路。
2. 高频数据结构考点:数组、链表、字符串的 C++ 实现细节
2.1 数组类题目:从「二维数组中的查找」看有序矩阵的解题套路
这道题是剑指 Offer 的第一道,也是面试中出现频率最高的题之一。题目描述:在一个二维数组中,每一行从左到右递增,每一列从上到下递增,判断某个数是否存在。
面试官最想听的答题思路是:从右上角开始比较。如果目标值小于当前值,说明目标不可能在当前列,列索引减一;如果大于当前值,说明目标不可能在当前行,行索引加一。这样每次排除一行或一列,时间复杂度 O(m+n)。
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) { if (matrix.empty() || matrix[0].empty()) return false; int rows = matrix.size(); int cols = matrix[0].size(); int row = 0, col = cols - 1; // 从右上角开始 while (row < rows && col >= 0) { if (matrix[row][col] == target) { return true; } else if (matrix[row][col] > target) { --col; // 当前列都大于 target,排除该列 } else { ++row; // 当前行都小于 target,排除该行 } } return false; }参数说明:matrix是二维向量,target是待查找整数。关键点在于起始位置选右上角,而不是左上角。左上角往两个方向都是递增,无法做出唯一决策。右上角往左递减、往下递增,每次比较都能排除一整行或一整列。
这类题在面试中经常被追问:如果矩阵不是方阵怎么办?如果允许重复元素呢?如果要求返回所有匹配位置呢?答题时先给出基础解法,再主动提一句「如果矩阵规模很大,可以考虑分块查找或二分优化」,面试官会觉得你考虑得全面。
2.2 链表类题目:反转链表与环检测的代码模板
链表题在 C++ 面试中几乎是必考。剑指 Offer 里涉及链表的题目有十几道,核心考点集中在指针操作和边界处理。
反转链表是最高频的一道。面试官通常会要求写迭代版本,然后追问递归版本。
// 迭代版本 ListNode* reverseList(ListNode* head) { ListNode* prev = nullptr; ListNode* curr = head; while (curr) { ListNode* nextTemp = curr->next; // 先保存下一个节点 curr->next = prev; // 反转指针 prev = curr; // prev 前移 curr = nextTemp; // curr 前移 } return prev; }这段代码的关键在于循环里的四步顺序不能乱。先保存next,再改curr->next,然后移动prev和curr。很多人在面试时写反了顺序,导致链表断裂。面试官看到这种错误会直接扣分,因为这是链表操作的基本功。
环检测题通常要求用快慢指针。答题模板是:快指针每次走两步,慢指针每次走一步,如果相遇则有环。追问「如何找到环的入口」时,需要推导数学关系:相遇点到入口的距离等于头节点到入口的距离。这个推导过程要能当场写出来,不能只背结论。
2.3 字符串类题目:替换空格与最长不含重复字符子串
字符串题在 C++ 面试中常考两类:原地修改和滑动窗口。
替换空格题要求把字符串中的每个空格替换成「%20」。如果从前往后替换,每次都要移动后面所有字符,时间复杂度 O(n²)。正确做法是先统计空格数量,计算出新长度,然后从后往前填充。
string replaceSpace(string s) { int count = 0; for (char c : s) { if (c == ' ') ++count; } int oldLen = s.size(); s.resize(oldLen + 2 * count); // 每个空格多出两个字符 int newLen = s.size(); for (int i = oldLen - 1, j = newLen - 1; i >= 0; --i) { if (s[i] == ' ') { s[j--] = '0'; s[j--] = '2'; s[j--] = '%'; } else { s[j--] = s[i]; } } return s; }从后往前填充是这类原地修改题的核心套路。面试官会追问:如果字符串是char*而不是string怎么办?需要手动管理内存,记得在函数内部分配新数组并返回,同时说明调用方负责释放。
最长不含重复字符子串用滑动窗口加哈希表。C++ 里可以用unordered_map<char, int>记录字符最后出现的位置,右指针遇到重复字符时,左指针跳到上次出现位置的下一位。这道题在面试中经常和「最小覆盖子串」一起考,建议把两个模板都练熟。
3. 算法思想考点:递归、动态规划、回溯的答题框架
3.1 递归与分治:从斐波那契到快速排序的思维转换
递归题在剑指 Offer 里占比很高,但面试官不只看你能不能写出递归,更看你是否理解递归的代价。
斐波那契数列是递归入门的经典题。直接写递归会超时,因为存在大量重复计算。面试时应该先指出这个问题,然后给出优化方案:要么用记忆化搜索,要么用迭代。
// 迭代版本,时间复杂度 O(n),空间 O(1) int fib(int n) { if (n < 2) return n; int a = 0, b = 1; for (int i = 2; i <= n; ++i) { int temp = (a + b) % 1000000007; // 取模防止溢出 a = b; b = temp; } return b; }面试官常追问:为什么取模?因为题目要求结果对 1e9+7 取模,防止整数溢出。C++ 中int通常是 32 位,两个大数相加可能溢出,所以要在每一步都取模。
分治思想的典型题是「重建二叉树」。给前序和中序遍历结果,重建二叉树。答题思路是:前序的第一个元素是根节点,在中序中找到根节点的位置,左边是左子树,右边是右子树,然后递归处理。这道题的关键是边界计算,面试时建议先画图确认索引范围,再写代码。
3.2 动态规划:状态定义与转移方程的答题模板
动态规划是面试中区分度最高的考点。剑指 Offer 里的 DP 题有十几道,覆盖了线性 DP、区间 DP、背包问题等。
以「剪绳子」为例。题目:一根长度为 n 的绳子,剪成 m 段(m、n 都是整数,n>1 且 m>1),每段长度乘积最大是多少。
面试时先定义状态:dp[i]表示长度为 i 的绳子剪成若干段后的最大乘积。转移方程:dp[i] = max(j * (i-j), j * dp[i-j]),其中 j 从 1 到 i-1。
int cuttingRope(int n) { vector<int> dp(n + 1, 0); dp[1] = 1; for (int i = 2; i <= n; ++i) { for (int j = 1; j < i; ++j) { // 剪成 j 和 i-j 两段,或者 j 和 dp[i-j] 继续剪 dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j])); } } return dp[n]; }这道题面试官会追问:为什么dp[1]初始化为 1?因为长度为 1 的绳子无法再剪,但作为子问题的一部分,它的乘积贡献是 1。如果初始化为 0,转移方程会出错。
另一道高频 DP 题是「礼物的最大价值」。在一个 m×n 的棋盘上,每个格子有礼物,从左上角走到右下角,每次只能向右或向下,求最大礼物价值。状态定义:dp[i][j]表示到达 (i,j) 时的最大价值。转移方程:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]。这道题可以优化空间到一维数组,面试时主动提出来是加分项。
3.3 回溯与剪枝:排列、组合、子集问题的统一解法
回溯题在面试中通常以「全排列」「组合总和」「子集」等形式出现。剑指 Offer 里的「字符串的排列」就是典型。
答题模板是:定义结果集、路径、选择列表,然后递归加回溯。
void backtrack(string& s, vector<bool>& used, string& path, vector<string>& res) { if (path.size() == s.size()) { res.push_back(path); return; } for (int i = 0; i < s.size(); ++i) { if (used[i]) continue; // 剪枝:相同字符在同一层只选一次 if (i > 0 && s[i] == s[i-1] && !used[i-1]) continue; used[i] = true; path.push_back(s[i]); backtrack(s, used, path, res); path.pop_back(); used[i] = false; } }剪枝条件是这道题的难点。s[i] == s[i-1] && !used[i-1]的意思是:如果当前字符和前一个字符相同,且前一个字符没有被使用,说明在同一层已经选过了,跳过。这个条件不写,结果会有重复排列。
面试官常追问:如果要求按字典序输出呢?可以在递归前对字符串排序,这样剪枝条件自然生效。如果要求去重但不排序呢?可以用unordered_set记录每层使用过的字符。
4. 易错题与失分点:面试官最常抓的五个漏洞
4.1 指针未初始化或未判空
错在哪:写链表题时直接ListNode* p = head->next,没有判断head是否为空。
为什么错:面试官会认为你缺乏防御性编程意识。C++ 中空指针解引用是未定义行为,实际项目中会导致崩溃。
怎么答对:任何涉及指针的操作,先判空。链表题开头统一写if (!head) return nullptr;。数组题先判断empty()。这个习惯要在刷题时就养成,面试时自然写出来。
4.2 整数溢出未处理
错在哪:斐波那契数列题直接return a + b,没有取模。
为什么错:题目明确要求对 1e9+7 取模,不取模会溢出。面试官会认为你没仔细读题。
怎么答对:看到题目有「答案对 XX 取模」的字样,每一步运算后都取模。C++ 中可以用long long临时存储,再取模转回int。
4.3 递归边界条件写错
错在哪:重建二叉树时,递归终止条件写成preStart > preEnd,但实际应该是preStart > preEnd时返回nullptr。
为什么错:边界条件差一,整个递归结果就错了。面试官会让你手动跑一个简单用例,立刻暴露。
怎么答对:写完递归后,用 n=1、n=2 的用例手动模拟一遍。确认终止条件、索引计算、返回值都正确。
4.4 动态规划状态定义不清晰
错在哪:剪绳子题直接写dp[i] = max(dp[i], j * dp[i-j]),漏掉了j * (i-j)的情况。
为什么错:状态转移方程不完整,说明对「剪成两段」和「继续剪」的区别没想清楚。
怎么答对:定义状态时先写注释:dp[i]表示什么。转移时枚举所有可能的选择,不要凭感觉写。写完用 n=4 验证:正确答案是 4(2×2),如果代码输出 3,说明漏了情况。
4.5 STL 容器使用不当
错在哪:用vector<int>做滑动窗口时,频繁erase头部元素,导致 O(n) 时间复杂度。
为什么错:vector头部删除是线性时间,应该用deque或双指针加索引。
怎么答对:根据操作特点选容器。频繁头部插入删除用deque,频繁随机访问用vector,频繁查找用unordered_set。面试时主动说出选择理由,面试官会认为你基础扎实。
5. 答题技巧与押题思路:从刷完题到拿 Offer 的最后一步
面试前一周,不要再按顺序刷题。把剑指 Offer 的题目按考点分类,每类挑 2-3 道手写一遍。手写的意思是:在白纸或白板上写,不借助 IDE 的自动补全和编译错误提示。C++ 的面试通常要求手写代码,语法错误和边界遗漏都会扣分。
押题思路方面,根据近几年 C++ 岗的面试反馈,以下考点出现频率最高:链表反转与环检测、二叉树遍历(递归+迭代)、动态规划(剪绳子、礼物最大价值、最长不含重复字符子串)、回溯(全排列、子集)、双指针(有序数组两数之和、三数之和)。这些题在剑指 Offer 里都有对应章节,建议优先练熟。
答题时遵循「三步走」:第一步,复述题目并确认边界条件,比如「数组是否有序」「是否允许重复」「返回值是什么」;第二步,说出思路和时间复杂度,再开始写代码;第三步,写完用简单用例验证,主动指出可能的优化方向。
提示:面试官允许你用伪代码描述思路,但最终要落到可编译的 C++ 代码。平时练习时就要注意
#include和命名空间,面试时写vector要加std::或提前声明using namespace std;。
我自己的血泪经验是:每次面试前强制走一遍「手写链表反转 + 二叉树层序遍历 + 剪绳子」这三道题。这三道题覆盖了指针、递归、动态规划三个核心考点,写顺了,面试时手不会生。从那以后我每次面试前都强制走一遍这个流程,再也没在基础题上翻过车。希望帮到你。
本文还有配套的精品资源,点击获取