概述
今天LeetCode的每日一题是设计一个跳表。跳表是一种类似链表的数据结构,可以实现时间复杂度为 O(log n)
的查找和插入,效率堪比红黑树。
常规的链表可以实现常数级时间复杂度的插入,但是查找某一个特定元素的元素却是线性的。而诸如数组和vector之类的简单线性的数据结构,虽然可以通过二分查找来实现 O(log n)
时间复杂度的查找,但是在某个特定位置插入一个元素需要把其后面所有元素都挪动一遍,牵一发而动全身。而AVL树、红黑树可以平衡插入和查找的效率,被广泛应用。今天刚学到的跳表也有相似的性质。
插一个小故事。就前两天我在看《STL源码剖析》试图理解红黑树的时候还看到一段话,说普通的链表查找某一个特定元素是线性的,我就想这个作者也太菜了,你不能二分查找吗?后来仔细一想,才想起来原来链表是没法随机访问的,自然不能二分查找,是我太菜了。
后来还是看不懂红黑树是什么原理。但是跳表就好理解多了。在LeetCode评论区里看到一条,“想了半天要如何确定一个值应该占用几层的链表,然后你给我来个coin filp,我遭不住了,底层逻辑基于随机数,怪不得STL里没你”,笑死我了。
扯了这么多,那么跳表到底是什么原理呢?用LeetCode上的一张图就可以看懂了。

查找
查找时从上往下依次找到每层小于目标值的最大的节点,然后依次往下查找,中间可以跳过很多个节点。因为可以有可以跳过节点这个性质,跳表查找的平均时间复杂度是O(log n)
,这个有大佬证明了,而我不是大佬所以我不会证明。
插入
插入时也依次从上往下找插入的节点,第0
层包含跳表内的所有元素,然后每个元素在插入时有p
的概率可以加入到上面那层,p
一般被设置为 1/2
。没错,跳表的结构是随机出来的,就是这么草率。
例题
LeetCode 1206. 设计跳表
就是设计一个上述跳表,要求实现查找、插入和删除三个功能。
示例 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 输入 ["Skiplist", "add", "add", "add", "search", "add", "search", "erase", "erase", "search"] [[], [1], [2], [3], [0], [4], [1], [0], [1], [1]] 输出 [null, null, null, null, false, null, true, false, true, false]
解释 Skiplist skiplist = new Skiplist(); skiplist.add(1); skiplist.add(2); skiplist.add(3); skiplist.search(0); skiplist.add(4); skiplist.search(1); skiplist.erase(0); skiplist.erase(1); skiplist.search(1);
|
题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| class Skiplist { private: class Node { public: int val; vector<Node* > next = vector<Node* >(10, nullptr); Node(int v) :val(v) {}; void insert(Node* nxt, int level) { vector<Node* >& cur = this->next; nxt->next[level] = cur[level]; cur[level] = nxt; return; } void erasenext(Node* nxt) { vector<Node* >& cur = this->next; for (int i = 0; i < 10; i++) { if (cur[i] == nxt) { cur[i] = cur[i]->next[i]; } } } }; Node* head; Node* tail; public: Skiplist() { head = new Node(-1); tail = new Node(1e9 + 7); for (int i = 0; i < 10; i++) { head->next[i] = tail; } }
bool search(int target) { Node* cur = head; for (int i = 9; i >= 0; i--) { while (cur->next[i]->val < target) cur = cur->next[i]; } return cur->next[0]->val == target; }
void add(int num) {
stack<Node* > stk; Node* cur = head; for (int i = 9; i >= 0; i--) { while (cur->next[i]->val < num) cur = cur->next[i]; stk.push(cur); } int level = 0; Node* newnode = new Node(num); cur->insert(newnode, level); while (level < 9 && rand() % 2 == 0) { level++; stk.pop(); stk.top()->insert(newnode, level); } }
bool erase(int num) { stack<Node* > stk; Node* cur = head; for (int i = 9; i >= 0; i--) { while (cur->next[i]->val < num) cur = cur->next[i]; stk.push(cur); } if (cur->next[0]->val != num) return false; else {
cur->erasenext(cur->next[0]); return true; } } };
|
有的没的
暑假生活
暑假在家真的宅烂了,今天跟两个初中同学去南虹吃了一整天。吃了臭豆腐、老长沙手工肠、颜记排骨、虾滑、两杯茶百道、KFC、炸串串,饱死了。

没事听点歌(胡歌 - 忘记时间)