从LeetCode刷题看STL容器选择:什么时候该用vector而不是list?

张开发
2026/4/14 19:38:47 15 分钟阅读

分享文章

从LeetCode刷题看STL容器选择:什么时候该用vector而不是list?
从LeetCode刷题看STL容器选择什么时候该用vector而不是list在算法竞赛和编程面试中选择合适的STL容器往往能事半功倍。很多初学者习惯性地在所有场景下使用vector却不知道这种一刀切的做法可能让代码效率大打折扣。本文将结合LeetCode高频题型深入分析vector、list和deque三大顺序容器的性能特点帮助你在不同场景下做出最优选择。1. 理解容器的底层实现差异1.1 vector连续内存的利与弊vector的底层是一个动态数组采用2倍扩容策略。这种设计带来了两个关键特性vectorint vec; // 初始容量为0 vec.reserve(100); // 预分配100个元素空间优势随机访问时间复杂度O(1)尾部插入/删除操作高效缓存友好数据局部性原理劣势中间插入/删除需要移动元素扩容时可能引起大量数据拷贝1.2 list链式结构的灵活性list实现为双向链表每个节点单独分配内存listint lst; lst.splice(lst.end(), lst, lst.begin()); // O(1)时间移动节点优势任意位置插入/删除都是O(1)没有扩容开销splice操作无需数据拷贝劣势随机访问需要O(n)时间内存不连续缓存命中率低1.3 deque折中的双端队列deque采用分块的二维数组结构操作vectordequelist头部插入O(n)O(1)O(1)尾部插入O(1)O(1)O(1)中间插入O(n)O(n)O(1)随机访问O(1)O(1)O(n)提示deque的O(1)随机访问实际上比vector慢2-3倍因为需要计算块位置2. LeetCode实战场景分析2.1 高频插入删除场景例题LeetCode 706. 设计哈希映射频繁插入删除// 错误示范使用vector处理频繁插入删除 vectorpairint, int hashMap; void put(int key, int value) { for(auto p : hashMap) { // O(n)查找 if(p.first key) { p.second value; return; } } hashMap.push_back({key, value}); // 可能触发扩容 } // 优化方案使用listvector组合 vectorlistpairint, int table; void put(int key, int value) { int hash key % table.size(); for(auto p : table[hash]) { // 只在单个桶中查找 if(p.first key) { p.second value; return; } } table[hash].push_back({key, value}); }2.2 随机访问密集型场景例题LeetCode 215. 数组中的第K个最大元素快速选择算法// vector是最佳选择 int findKthLargest(vectorint nums, int k) { // 利用vector的随机访问特性 nth_element(nums.begin(), nums.begin()k-1, nums.end(), greaterint()); return nums[k-1]; } // 如果使用list则效率极低 int findKthLargest(listint nums, int k) { // 需要转换为vector或手动实现算法 vectorint vec(nums.begin(), nums.end()); nth_element(vec.begin(), vec.begin()k-1, vec.end(), greaterint()); return vec[k-1]; }2.3 滑动窗口问题例题LeetCode 239. 滑动窗口最大值需要双端操作// deque的双端操作优势 vectorint maxSlidingWindow(vectorint nums, int k) { dequeint dq; vectorint res; for(int i0; inums.size(); i) { // 维护单调递减队列 while(!dq.empty() nums[dq.back()]nums[i]) dq.pop_back(); // 尾部删除 dq.push_back(i); // 尾部插入 if(dq.front()i-k) dq.pop_front(); // 头部删除 if(ik-1) res.push_back(nums[dq.front()]); } return res; }3. 性能优化实战技巧3.1 vector的reserve魔法在已知数据量时预分配空间避免多次扩容// LeetCode 118. 杨辉三角 vectorvectorint generate(int numRows) { vectorvectorint res; res.reserve(numRows); // 关键优化 for(int i0; inumRows; i) { vectorint row(i1, 1); for(int j1; ji; j) { row[j] res[i-1][j-1] res[i-1][j]; } res.push_back(move(row)); // 移动语义减少拷贝 } return res; }3.2 list的splice妙用合并两个有序链表时的高效操作// LeetCode 21. 合并两个有序链表list版本 listint mergeTwoLists(listint l1, listint l2) { listint merged; auto it1 l1.begin(), it2 l2.begin(); while(it1 ! l1.end() it2 ! l2.end()) { if(*it1 *it2) { merged.splice(merged.end(), l1, it1); } else { merged.splice(merged.end(), l2, it2); } } merged.splice(merged.end(), l1); // 剩余元素整体移动 merged.splice(merged.end(), l2); return merged; }3.3 容器选择的决策树根据问题特征快速选择容器是否需要频繁随机访问是 → 选择vector否 → 进入下一问题是否需要频繁在头部/中间插入删除头部操作多 → 选择deque中间操作多 → 选择list仅尾部操作 → 选择vector数据规模是否已知已知 → vectorreserve未知 → 考虑内存碎片问题4. 常见误区与最佳实践4.1 避免无脑vector的陷阱错误案例LeetCode 146. LRU缓存频繁在中间插入删除// 错误使用vector实现LRU vectorpairint, int cache; int get(int key) { for(int i0; icache.size(); i) { // O(n)查找 if(cache[i].first key) { auto val cache[i].second; cache.erase(cache.begin()i); // O(n)删除 cache.insert(cache.begin(), {key, val}); // O(n)插入 return val; } } return -1; } // 正确使用listunordered_map listpairint, int cache; unordered_mapint, listpairint, int::iterator map; int get(int key) { if(map.count(key)) { auto it map[key]; cache.splice(cache.begin(), cache, it); // O(1)移动 return it-second; } return -1; }4.2 迭代器失效的坑不同容器的迭代器失效规则容器插入操作删除操作vector所有迭代器可能失效被删元素及之后迭代器失效deque除插入点外可能失效除被删元素外可能失效list不会使任何迭代器失效仅使被删元素迭代器失效// 危险代码遍历时删除vector元素 vectorint vec {1,2,3,4,5}; for(auto itvec.begin(); it!vec.end(); it) { if(*it % 2 0) { vec.erase(it); // 迭代器it失效 } } // 安全做法 for(auto itvec.begin(); it!vec.end(); ) { if(*it % 2 0) { it vec.erase(it); // erase返回下一个有效迭代器 } else { it; } }4.3 现代C的优化技巧利用C11/14/17特性提升容器性能// 移动语义减少拷贝 vectorstring processLargeData() { vectorstring data getHugeData(); return data; // NRVO优化或移动语义 } // emplace_back直接构造 vectorcomplexObj vec; vec.emplace_back(arg1, arg2); // 避免临时对象 // 结构化绑定(C17) unordered_mapstring, int wordCount; for(const auto [word, count] : wordCount) { cout word : count endl; }在实际刷题过程中我经常看到选手因为容器选择不当导致TLE时间限制 exceeded。有一次在解决图论问题时使用vector存储邻接表却频繁在头部插入结果性能比预期慢了10倍。换成list后立即通过这个教训让我深刻理解了容器特性对算法效率的影响。

更多文章