1. 项目概述:力扣刷题的C++实践记录
最近在整理自己的力扣刷题笔记时,发现C++解法往往能提供独特的视角和高效的实现。这篇日记将分享我在解决150(逆波兰表达式求值)、239(滑动窗口最大值)、347(前K个高频元素)和71(简化路径)这四道题目时的思考过程和代码实现。这些题目覆盖了栈、队列、堆等数据结构的典型应用场景,对准备技术面试很有帮助。
2. 题目解析与实现思路
2.1 150. 逆波兰表达式求值
逆波兰表达式(后缀表达式)的计算是栈结构的经典应用。遇到数字就压栈,遇到运算符就弹出栈顶两个元素进行计算,再将结果压回栈中。
int evalRPN(vector<string>& tokens) { stack<int> st; for (const auto& token : tokens) { if (token == "+" || token == "-" || token == "*" || token == "/") { int b = st.top(); st.pop(); int a = st.top(); st.pop(); if (token == "+") st.push(a + b); else if (token == "-") st.push(a - b); else if (token == "*") st.push(a * b); else st.push(a / b); } else { st.push(stoi(token)); } } return st.top(); }注意:除法的处理要特别注意,题目保证除法结果会向零截断,所以直接使用整数除法即可。
2.2 239. 滑动窗口最大值
这道题要求我们在O(n)时间复杂度内解决,普通的暴力解法无法满足要求。这里使用双端队列维护一个单调递减的队列:
vector<int> maxSlidingWindow(vector<int>& nums, int k) { deque<int> dq; vector<int> res; for (int i = 0; i < nums.size(); ++i) { // 移除超出窗口范围的元素 if (!dq.empty() && dq.front() == i - k) dq.pop_front(); // 维护单调递减队列 while (!dq.empty() && nums[dq.back()] < nums[i]) dq.pop_back(); dq.push_back(i); // 窗口形成后记录结果 if (i >= k - 1) res.push_back(nums[dq.front()]); } return res; }2.3 347. 前K个高频元素
这道题考察堆的使用。我们可以先用哈希表统计频率,然后用最小堆来维护前K个高频元素:
vector<int> topKFrequent(vector<int>& nums, int k) { unordered_map<int, int> freq; for (int num : nums) freq[num]++; auto cmp = [&freq](int a, int b) { return freq[a] > freq[b]; }; priority_queue<int, vector<int>, decltype(cmp)> pq(cmp); for (auto& p : freq) { pq.push(p.first); if (pq.size() > k) pq.pop(); } vector<int> res; while (!pq.empty()) { res.push_back(pq.top()); pq.pop(); } return res; }2.4 71. 简化路径
这道题需要处理Unix风格的路径字符串,使用栈来处理路径中的"."和"..":
string simplifyPath(string path) { vector<string> st; stringstream ss(path); string dir; while (getline(ss, dir, '/')) { if (dir.empty() || dir == ".") continue; if (dir == "..") { if (!st.empty()) st.pop_back(); } else { st.push_back(dir); } } string res; for (const auto& s : st) { res += "/" + s; } return res.empty() ? "/" : res; }3. 解题技巧与优化思路
3.1 数据结构的选择与比较
在解决这些问题时,数据结构的选择至关重要:
- 栈:适合处理对称性、递归性问题(如150题)
- 队列/双端队列:适合处理滑动窗口、BFS相关问题(如239题)
- 堆:适合处理Top K问题(如347题)
- 哈希表:适合快速查找和频率统计
3.2 C++标准库的高效使用
C++标准库提供了丰富的数据结构实现,合理使用可以大幅提升编码效率:
std::stack和std::queue是适配器容器,底层默认使用dequestd::priority_queue默认是大顶堆,可以通过自定义比较函数实现小顶堆std::unordered_map比std::map有更好的平均时间复杂度(O(1) vs O(log n))
3.3 边界条件处理
在刷题过程中,边界条件的处理往往决定成败:
- 空输入的处理
- 极端值(如最大/最小值)的处理
- 特殊字符或格式的处理
- 循环终止条件的判断
4. 常见问题与调试技巧
4.1 调试技巧
- 打印中间结果:在关键步骤后打印变量值
- 使用断言:验证不变量和前提条件
- 单元测试:为每个函数编写测试用例
4.2 性能优化
- 减少不必要的拷贝:使用引用传递参数
- 预分配内存:对于已知大小的容器,提前reserve
- 选择合适的数据结构:根据操作特点选择最优结构
4.3 编码风格建议
- 变量命名要有意义
- 适当添加注释解释复杂逻辑
- 保持函数短小精悍,单一职责
- 错误处理要明确
5. 力扣刷题的系统性方法
5.1 题目分类训练
建议按照题目类型进行集中训练,比如:
- 数组与字符串
- 链表
- 树与图
- 动态规划
- 贪心算法
5.2 错题本与复盘
建立自己的错题本,记录:
- 初次解题的思路
- 遇到的坑和错误
- 最优解的分析
- 同类题目的变形
5.3 时间管理与模拟面试
在准备面试时:
- 限时练习(如30分钟一题)
- 模拟白板编程
- 练习口头解释思路
6. C++刷题的特殊考量
6.1 语言特性利用
- 移动语义:减少不必要的拷贝
- 智能指针:管理动态内存
- Lambda表达式:简化回调实现
6.2 标准库算法
熟练使用<algorithm>中的常用函数:
sort/stable_sortlower_bound/upper_boundnext_permutationaccumulate
6.3 内存与性能
- 注意容器扩容的成本
- 避免不必要的动态内存分配
- 考虑缓存友好性
刷题不仅是解决问题的过程,更是培养计算思维和编程习惯的好方法。通过系统地记录和复盘,可以显著提升算法能力和编码水平。