LeetCode题解整理

LeetCode题解整理

2025-3-19

SSL 协议这篇博客时写到了 C++ 的 unordered_map 用的是 FNV 哈希,算是除了当年分析排序算法之外第一次试图理解 STL 的底层实现,让我回忆起 2022 年的 3 月 (25号~9月18号),在残酷刷题群里麻木刷题时不懂底层道理只会调用 STL 的难受。在等待训练 chemprop 作为 molgen 奖励函数的时候,重新调通三年没碰的 macOS vscode cpp 工作流,试着借助 chatgpt 搞懂 STL 容器。

vector

vector 底层实现:动态数组(dynamic array,连续内存快)

C++ 的 std::vector、Java 的 ArrayList、Python 的 list 都是动态数组的实现,数组的元素在内存中是连续存储的。

时间复杂度:

  • 读写:O(1),内存中连续存储所以支持快速随机访问。
  • 增删:如图所示,在元素占满数组时会开两倍内存,并把内容复制过去。O(n)。若不满:
    • 尾部插入删除:O(1)
    • 中间或前部插入删除:O(n),保持内存连续。

list

list 底层实现:双向链表(doubly linked list)

C++ 的 std::list、Java 的 java.util.LinkedList、Python 的 collections.deque(下文讲) 都是双向链表的实现。如图每个结点比单向链表多维护一个 prev 指针,因此在 O(1) 的时间内可以获取前驱结点,进而加速了插入和删除操作。

操作 时间复杂度 说明
读写 由于链表不支持随机访问,必须从头或尾部遍历到目标位置
头部插入删除(Insert or Delete at Head) 直接修改 head 指针,不需要遍历
尾部插入删除(Insert or Delete at Tail) 直接修改 tail 指针,不需要遍历
中间插入删除(Insert or Delete at Index k) 需要遍历到索引 ,然后插入或删除

deque

deque, double-ended queue, 双端队列

C++:std::deque(动态数组实现,写到这才发现有官方文档:https://cplusplus.com/reference/deque/ ),Java:ArrayDeque(动态数组实现)、LinkedList(实现了 Deque 接口,双向链表实现),Python:collections.deque(双向链表实现)。

着重讲一下 deque,因为 C++ 和 python 的 stack 和 queue 都是用 deque 实现的。在 C++ 里管 stack 和 queue 叫 Adaptors(适配器),指的是不直接存储数据,用已有容器(underlying container)提供新的接口,使其行为满足特定需求的特殊容器;在 python 里管 deques 叫做 stacks 和 queues 们的泛化(generalization)

C++ 的 deque:分块(chunks)存储,每个内存块chunk都是定长的vector,用一个指针队列(map,如图所示)管理这些内存块,保持物理不连续但逻辑连续,map本身也是一个vector。deque跟vector有些像,内部结构稍微复杂一些,在存非常长的序列时deque会更高效地扩展,因为vector此时重新分配内存(reallocation, O(n))会开销很大。
这篇文章gif失效了。

在 Python 里 deque 是用双向链表串连起内存块。3.8.1的源码在这。与之类似的 list(是C语言优化的指针数组)在增删头部(pop(0) 或 insert(0, v))时有 O(n) 的内存移动成本,但对固定长度的快速操作做了优化。

stack

LIFO (last-in first-out)

C++有std::stack(deque的adaptor)Java有Stack<E>(Java的栈顶叫peek,C++和Python都叫top),Python里可以用list、deque模拟。

操作 时间复杂度
入栈 (push)
出栈 (pop)
访问栈顶 (top/peek)
查找 (search)

queue

FIFO (first-in first-out)

C++有std::queue(deque的adaptor),Java有Queue<E>,Python里提供了线程安全的队列实现。

操作 时间复杂度
入队 (enqueue)
出队 (dequeue)
访问队首 (front)
查找 (search)

priority_queue

priority_queue是大根堆,即每个结点的值都小于等于其父结点,堆是一棵树。
大根堆主要支持的操作有:插入一个数、查询最大值、删除最大值、合并两个堆、减小一个元素的值。
可并堆支持高效合并。
可持久化,是对任意历史版本进行查询或者操作,产生新的版本。
priority_queue往往是二叉堆,提到堆也往往指二叉堆。

下表来自oi-wikioi-wiki 对下面三个数据结构解释得都不错,建议直接看oi-wiki,这里只作为笔记。

操作 \ 数据结构 二叉堆 配对堆 左偏树 二项堆 斐波那契堆
插入(insert) $O(\log n)$ $O(1)$ $O(\log n)$ $O(\log n)$ $O(1)$
查询最大值(find-max) $O(1)$ $O(1)$ $O(1)$ $O(1)$ $O(1)$
删除最大值(delete-max) $O(\log n)$ $O(\log n)$ $O(\log n)$ $O(\log n)$ $O(\log n)$
合并 (merge) $O(n)$ $O(1)$ $O(\log n)$ $O(\log n)$ $O(1)$
减小一个元素的值 (decrease-key) $O(\log n)$ $o(\log n)$(下界 $\Omega(\log \log n)$,上界 $O(2^{2\sqrt{\log \log n}})$) $O(\log n)$ $O(\log n)$ $O(1)$
是否支持可持久化 $\checkmark$ $\times$ $\checkmark$ $\checkmark$ $\times$

increase key呢?

详细复习一下七年前学的二叉堆和二叉搜索树:
Why is Binary Heap is better than BST for Priority Queue?
What is the underlying data structure of a STL set in C++?
玩熟练这下面这仨复杂高效的 STL 容器的数据结构(二叉堆、二叉搜索树(→二叉平衡树→红黑树)、哈希桶)基本就对算法题的数据结构得心应手了吧。

二叉堆Binary Heap

二叉搜索树Binary Search Tree

map

红黑树

unordered_map

哈希表

set

红黑树

unordered_set

哈希表

附图

肄业倒计时

今天交完房租,结束了在这个出租屋的第 25 个月。感谢邻居早 8 点准时响起的电钻,把我赶来北街咖啡,赶来大食堂。无论是以前还是现在,在家里一个人呆着只会让我腐烂发臭,turn into the Rotten of Black Gulch。

肄业(yì yè, Dropping Out),肆业(sì yè, 勤于所业)。根据汉典,肄的意思有:「肄,習也。」「肄,嫩條也。」「肄,勞也。」算是个“退学”的仁慈版本。

By all means marry. If you get a good wife, you’ll be happy. If you get a bad one, you’ll become a philosopher. (Socrates,苏格拉底有个好老婆还是坏老婆呢)

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×