1. LeetCode刷题与C++ STL实战指南
作为一名在算法竞赛和工业界摸爬滚打多年的老码农,我深知LeetCode刷题和C++ STL熟练度对职业发展的重要性。这份指南不是简单的API罗列,而是结合我数百小时刷题和实际工程经验提炼出的实战手册。无论你是准备校招的应届生,还是想提升算法能力的中级开发者,都能从中找到可立即落地的技巧。
2. 科学刷题方法论
2.1 平台选择与策略制定
力扣(LeetCode)和牛客网是当前最主流的两个刷题平台,但它们的定位略有不同:
- 力扣:题目质量高,社区活跃,特别适合系统性地提升算法能力。建议按「标签分类」刷题,比如先集中攻克「二叉树」相关题目,再转向「动态规划」
- 牛客网:企业真题多,适合模拟面试场景。建议在面试前1-2个月开始针对性练习
我的经验是:先用力扣打基础,再用牛客练实战。刷题初期每天3-5题,保持连续性和节奏感比突击更重要。
2.2 高效学习路径
根据难度梯度制定学习计划:
-
入门阶段(1-2个月):
- 掌握基础数据结构:数组、链表、栈、队列、哈希表
- 熟悉时间复杂度分析
- 完成力扣「初级算法」分类下的全部题目
-
进阶阶段(3-4个月):
- 深入理解递归和回溯
- 掌握动态规划的核心思想
- 攻克「中级算法」和部分「高级算法」题目
-
冲刺阶段(1个月):
- 限时模拟真实面试环境
- 重点复习高频考题和易错题
- 参加周赛锻炼临场发挥能力
3. C++ STL深度解析
3.1 vector:动态数组的工程实践
3.1.1 性能优化技巧
cpp复制// 不好的实践:频繁push_back导致多次扩容
vector<int> nums;
for(int i=0; i<1000000; i++){
nums.push_back(i); // 可能触发多次内存重分配
}
// 优化方案:预先分配足够空间
vector<int> nums;
nums.reserve(1000000); // 一次性分配足够内存
for(int i=0; i<1000000; i++){
nums.push_back(i); // 不会触发扩容
}
扩容机制:vector在空间不足时会按当前容量的2倍扩容(不同编译器实现可能不同),这个过程涉及:
- 申请新内存
- 拷贝原有元素
- 释放旧内存
实测数据:预先reserve比不reserve在百万级数据插入时快3-5倍
3.1.2 多维vector的高级用法
cpp复制// 三维动态数组声明
vector<vector<vector<int>>> cube(5, vector<vector<int>>(5, vector<int>(5)));
// 不规则二维数组(每行长度不同)
vector<vector<int>> jagged;
jagged.push_back({1});
jagged.push_back({2,3});
jagged.push_back({4,5,6});
3.2 unordered_map:哈希表的实战细节
3.2.1 自定义哈希函数
cpp复制struct Point {
int x, y;
bool operator==(const Point& p) const {
return x == p.x && y == p.y;
}
};
struct PointHash {
size_t operator()(const Point& p) const {
return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
}
};
unordered_map<Point, int, PointHash> pointMap;
3.2.2 性能陷阱与规避
cpp复制// 不好的实践:频繁查询不存在的key
unordered_map<string, int> wordCount;
if(wordCount["hello"] > 0){ // 自动插入"hello"并初始化为0
// ...
}
// 正确做法:先用count检查存在性
if(wordCount.count("hello") && wordCount["hello"] > 0){
// ...
}
负载因子:当元素数量与桶数量的比值超过max_load_factor(默认1.0)时,会自动rehash。可以通过以下方式优化:
cpp复制unordered_map<int, int> mp;
mp.reserve(1024); // 预分配桶数量
mp.max_load_factor(0.75); // 设置更严格的负载因子阈值
3.3 priority_queue:堆的高级应用
3.3.1 自定义比较函数
cpp复制// 小顶堆的三种实现方式
// 方式1:使用greater
priority_queue<int, vector<int>, greater<int>> minHeap1;
// 方式2:自定义仿函数
struct Compare {
bool operator()(int a, int b) {
return a > b;
}
};
priority_queue<int, vector<int>, Compare> minHeap2;
// 方式3:lambda表达式(C++20)
auto cmp = [](int a, int b) { return a > b; };
priority_queue<int, vector<int>, decltype(cmp)> minHeap3(cmp);
3.3.2 复杂数据结构的堆应用
cpp复制// 存储pair并自定义排序
priority_queue<pair<int, string>, vector<pair<int, string>>,
function<bool(pair<int,string>, pair<int,string>)>>
pq([](auto& a, auto& b) { return a.first > b.first; });
4. 算法模板与优化技巧
4.1 二分查找的工程实现
cpp复制// 标准二分查找模板
int binarySearch(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while(left <= right) {
int mid = left + (right - left) / 2; // 避免溢出
if(nums[mid] == target) {
return mid;
} else if(nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
// 寻找左边界变种
int leftBound(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] >= target) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
关键点:mid计算方式、循环条件、边界更新这三个要素的组合决定二分查找的变种行为
4.2 快速排序的工程级实现
cpp复制// 三向切分快速排序(适合有大量重复元素的情况)
void quickSort3Way(vector<int>& nums, int low, int high) {
if(low >= high) return;
int lt = low, gt = high;
int pivot = nums[low];
int i = low + 1;
while(i <= gt) {
if(nums[i] < pivot) {
swap(nums[lt++], nums[i++]);
} else if(nums[i] > pivot) {
swap(nums[i], nums[gt--]);
} else {
i++;
}
}
quickSort3Way(nums, low, lt - 1);
quickSort3Way(nums, gt + 1, high);
}
性能对比:
- 标准快排:随机数据表现好,但重复数据退化为O(n²)
- 三向快排:重复数据多时性能更优,保持O(nlogn)
5. 工程实践中的常见陷阱
5.1 迭代器失效问题
cpp复制// 错误示例:在遍历时删除元素
vector<int> nums = {1,2,3,4,5};
for(auto it = nums.begin(); it != nums.end(); ++it) {
if(*it % 2 == 0) {
nums.erase(it); // 错误!erase后it失效
}
}
// 正确写法1:利用erase返回值
for(auto it = nums.begin(); it != nums.end(); ) {
if(*it % 2 == 0) {
it = nums.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
// 正确写法2:C++20起可用std::erase_if
std::erase_if(nums, [](int n) { return n % 2 == 0; });
5.2 隐式类型转换陷阱
cpp复制vector<int> nums = {1,2,3,4,5};
size_t size = nums.size();
// 危险操作:有符号和无符号比较
for(int i=0; i<nums.size()-1; i++) {
// 当nums为空时,nums.size()-1会变成非常大的数
// ...
}
// 安全写法1:统一使用size_t
for(size_t i=0; i+1<size; i++)
// 安全写法2:C++20起可用ssize()
for(ptrdiff_t i=0; i<std::ssize(nums)-1; i++)
6. 性能调优实战
6.1 输入输出加速
cpp复制// 关闭同步流(提速5-10倍)
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 配合使用更快的输入方式
int n;
cin >> n;
vector<int> nums(n);
for(int i=0; i<n; i++) {
cin >> nums[i];
}
// 或者使用C风格输入(更快)
int n;
scanf("%d", &n);
vector<int> nums(n);
for(int i=0; i<n; i++) {
scanf("%d", &nums[i]);
}
6.2 内存池优化
对于频繁分配释放的小对象,可以使用内存池技术:
cpp复制// 简单内存池实现示例
class MemoryPool {
struct Block {
Block* next;
};
Block* freeList = nullptr;
size_t blockSize;
public:
MemoryPool(size_t size) : blockSize(size) {}
void* allocate() {
if(!freeList) {
freeList = static_cast<Block*>(::operator new(blockSize));
freeList->next = nullptr;
}
Block* block = freeList;
freeList = freeList->next;
return block;
}
void deallocate(void* ptr) {
Block* block = static_cast<Block*>(ptr);
block->next = freeList;
freeList = block;
}
};
7. 现代C++特性应用
7.1 结构化绑定(C++17)
cpp复制unordered_map<string, int> wordCount = {{"hello", 3}, {"world", 5}};
// 传统遍历方式
for(const auto& pair : wordCount) {
cout << pair.first << ": " << pair.second << endl;
}
// 结构化绑定(C++17)
for(const auto& [word, count] : wordCount) {
cout << word << ": " << count << endl;
}
7.2 移动语义优化
cpp复制// 返回大对象的优化写法
vector<int> createLargeVector() {
vector<int> result(1000000);
// ...填充数据
return result; // 编译器会自动应用移动语义
}
// 接收参数的优化
void processVector(vector<int>&& data) { // 右值引用
// ...处理数据
}
auto vec = createLargeVector();
processVector(std::move(vec)); // 明确转移所有权
8. 多线程并发编程
8.1 线程安全容器使用
cpp复制#include <mutex>
#include <shared_mutex>
class ThreadSafeMap {
private:
unordered_map<string, string> data;
mutable shared_mutex mutex; // C++17
public:
string get(const string& key) const {
shared_lock lock(mutex); // 共享锁,允许多读
auto it = data.find(key);
return it != data.end() ? it->second : "";
}
void set(const string& key, const string& value) {
unique_lock lock(mutex); // 独占锁,单写
data[key] = value;
}
};
8.2 并行算法(C++17)
cpp复制#include <execution>
vector<int> nums(1000000);
// 并行排序
sort(std::execution::par, nums.begin(), nums.end());
// 并行累加
int sum = reduce(std::execution::par, nums.begin(), nums.end());
9. 实战问题解析
9.1 经典问题:LRU缓存实现
cpp复制class LRUCache {
private:
struct Node {
int key, value;
Node *prev, *next;
Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {}
};
unordered_map<int, Node*> cache;
Node *head, *tail;
int capacity;
void moveToHead(Node* node) {
removeNode(node);
addToHead(node);
}
void removeNode(Node* node) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
void addToHead(Node* node) {
node->next = head->next;
node->prev = head;
head->next->prev = node;
head->next = node;
}
Node* removeTail() {
Node* node = tail->prev;
removeNode(node);
return node;
}
public:
LRUCache(int capacity) : capacity(capacity) {
head = new Node(-1, -1);
tail = new Node(-1, -1);
head->next = tail;
tail->prev = head;
}
int get(int key) {
if(!cache.count(key)) return -1;
Node* node = cache[key];
moveToHead(node);
return node->value;
}
void put(int key, int value) {
if(cache.count(key)) {
Node* node = cache[key];
node->value = value;
moveToHead(node);
} else {
if(cache.size() == capacity) {
Node* removed = removeTail();
cache.erase(removed->key);
delete removed;
}
Node* node = new Node(key, value);
cache[key] = node;
addToHead(node);
}
}
};
9.2 海量数据处理技巧
问题:如何在1GB内存下统计10GB文件中出现频率最高的100个单词?
解决方案:
- 分块处理:将文件分成多个小块,每块大小小于内存限制
- 哈希分片:对每个单词取哈希值,根据哈希值分配到不同文件
- 局部统计:对每个分片文件统计词频
- 归并统计:合并所有分片的统计结果
- 堆排序:维护大小为100的小顶堆,找出最高频单词
cpp复制// 伪代码示例
void processLargeFile() {
// 第一步:分片处理
for(const auto& chunk : splitFile("large.txt", 100MB)) {
unordered_map<string, int> localCount;
for(const auto& word : extractWords(chunk)) {
localCount[word]++;
}
writeToShard(localCount, hash(word) % SHARD_NUM);
}
// 第二步:归并统计
unordered_map<string, int> globalCount;
for(int i=0; i<SHARD_NUM; i++) {
auto shardCount = readShard(i);
for(const auto& [word, cnt] : shardCount) {
globalCount[word] += cnt;
}
}
// 第三步:TopK统计
priority_queue<pair<int, string>, vector<pair<int, string>>, greater<>> minHeap;
for(const auto& [word, cnt] : globalCount) {
minHeap.push({cnt, word});
if(minHeap.size() > 100) {
minHeap.pop();
}
}
// 输出结果
vector<pair<int, string>> result;
while(!minHeap.empty()) {
result.push_back(minHeap.top());
minHeap.pop();
}
reverse(result.begin(), result.end());
}
10. 持续学习建议
- 定期复习:建立自己的错题本,每月回顾一次易错题型
- 参与竞赛:参加LeetCode周赛和双周赛,锻炼编码速度和临场发挥
- 源码学习:阅读STL实现源码(如libstdc++),理解底层原理
- 系统学习:补充《算法导论》等经典教材的理论知识
- 工程实践:将算法应用于实际项目,比如用Trie树实现敏感词过滤
最后分享一个调试技巧:在VS Code中配置以下launch.json可以方便地调试LeetCode题目:
json复制{
"version": "0.2.0",
"configurations": [
{
"name": "Debug LeetCode",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/a.out",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "build",
"miDebuggerPath": "/usr/bin/gdb"
}
]
}