最近帮团队面试了几位C++后端开发候选人,发现不少人在算法环节表现欠佳。这让我意识到,算法能力依然是后端工程师的核心竞争力之一。特别是在大厂面试中,算法题的权重往往占到技术面试的50%以上。
这个系列已经更新到第七期,每期精选5道高频大厂算法题。不同于普通的题库,我会结合自己作为面试官的经验,重点讲解题目背后的考察点和工程实践中的实际应用场景。所有代码都经过LeetCode测试,确保正确性和最优时间复杂度。
本期选取的5道题目覆盖了以下技术点:
这些题目全部来自近半年国内一线互联网公司的真实面试题,难度集中在LeetCode Medium到Hard级别。
资源调度问题(DP)
考察动态规划的状态转移优化能力,类似实际工程中的任务调度场景
分布式拓扑排序(图论)
考察图的邻接表表示法和拓扑排序应用,对应微服务依赖解析场景
位图压缩算法(位运算)
考察位运算的工程优化技巧,实际应用于Redis等系统的位图存储
服务依赖树(树形DP)
考察树形动态规划建模能力,对应服务启动顺序决策场景
流量控制窗口(滑动窗口)
考察滑动窗口算法的工程实现,直接对应TCP流量控制场景
问题描述:
给定n个任务和m种资源,每个任务需要不同数量的各种资源,且完成有不同收益。在资源总量限制下,求最大收益。
这是典型的二维背包问题变种。面试官想考察:
cpp复制int maxProfit(vector<vector<int>>& resources, vector<int>& limits, vector<int>& profits) {
vector<vector<int>> dp(limits[0]+1, vector<int>(limits[1]+1, 0));
for (int i = 0; i < resources.size(); ++i) {
for (int j = limits[0]; j >= resources[i][0]; --j) {
for (int k = limits[1]; k >= resources[i][1]; --k) {
dp[j][k] = max(dp[j][k],
dp[j-resources[i][0]][k-resources[i][1]] + profits[i]);
}
}
}
return dp[limits[0]][limits[1]];
}
关键点:这里使用逆序更新避免重复计算,时间复杂度O(nmk),空间复杂度O(m*k)
问题描述:
给定微服务之间的依赖关系,判断是否存在合法的启动顺序,避免循环依赖。
实际工程中常用于:
cpp复制bool canFinish(int numServices, vector<vector<int>>& dependencies) {
vector<vector<int>> graph(numServices);
vector<int> inDegree(numServices, 0);
for (auto& dep : dependencies) {
graph[dep[1]].push_back(dep[0]);
inDegree[dep[0]]++;
}
queue<int> q;
for (int i = 0; i < numServices; ++i) {
if (inDegree[i] == 0) q.push(i);
}
int count = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
count++;
for (int v : graph[u]) {
if (--inDegree[v] == 0) {
q.push(v);
}
}
}
return count == numServices;
}
注意:邻接表存储方式比邻接矩阵更节省空间,适合稀疏图
问题描述:
实现一个位图压缩器,支持set/get操作,要求空间效率最大化。
这是Redis等系统底层使用的关键技术,考察:
cpp复制class Bitmap {
private:
vector<uint32_t> data;
public:
Bitmap(int size) : data((size + 31) / 32, 0) {}
void set(int pos) {
data[pos/32] |= (1 << (pos%32));
}
bool get(int pos) {
return (data[pos/32] >> (pos%32)) & 1;
}
};
技巧:使用uint32_t而不是bool数组,节省8倍空间。位运算中/和%可以用移位优化
问题描述:
给定服务调用树,每个节点有启动耗时,要求所有依赖服务都启动后才能启动当前服务,求最小总启动时间。
对应实际场景:
cpp复制int minStartTime(TreeNode* root) {
if (!root) return 0;
int maxChildTime = 0;
for (auto child : root->children) {
maxChildTime = max(maxChildTime, minStartTime(child));
}
return maxChildTime + root->time;
}
优化点:可以加入记忆化存储已计算结果,避免重复计算
问题描述:
实现一个带时间窗口的流量计数器,统计最近1分钟内的请求数。
这是TCP拥塞控制、API限流等场景的基础算法。
cpp复制class RequestCounter {
private:
queue<int> timestamps;
public:
void hit() {
int now = time(nullptr);
timestamps.push(now);
purge(now);
}
int getCount() {
purge(time(nullptr));
return timestamps.size();
}
void purge(int now) {
while (!timestamps.empty() &&
timestamps.front() <= now - 60) {
timestamps.pop();
}
}
};
工程实践:生产环境通常改用环形缓冲区,避免频繁内存分配
问题识别(30秒):
复杂度预估(1分钟):
边界处理(必须检查):
变量命名:
cpp复制// Bad
int a, b;
// Good
int leftBound, rightBound;
防御性编程:
cpp复制if (root == nullptr) { // 而不是 if (!root)
return 0;
}
注释要点:
DP问题:
图论问题:
树形问题:
LeetCode
重点练习:企业题库中的高频题目
Codeforces
锻炼快速编码能力和数学思维
AtCoder
学习日本选手的简洁代码风格
《算法导论》
动态规划、图论等理论基础
《编程珠玑》
位运算等工程优化技巧
《C++标准库》
熟悉STL容器和算法
项目中的应用:
代码Review:
性能分析: