Google - Design Skiplist
1. Requirements
-
insert
-
search
-
erase:
2. Thinking Process
Level 3: 1 ----------- 9 ----------- 20
Level 2: 1 ---- 5 ---- 9 ---- 14 ---- 20
Level 1: 1 - 3 - 5 - 7 - 9 - 12 - 14 - 17 - 20
Level 0: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
-
Insert: Add item to level 0 first, random to update to level you want => But when add to level 2, must add to level 0 and level 1.
-
Search: search from Level 3 -> Level 2 -> Level 1 -> Level 0.
3. Code Walk-through
import random
MAX_LEVEL = 16 # limit height for simplicity
P_FACTOR = 0.5 # probability to grow a level (like coin toss)
class Node:
"""Each node stores a value and forward pointers to next nodes at different levels."""
def __init__(self, val, level):
self.val = val
self.forward = [None] * (level + 1) # forward[i] points to next node at level i
class Skiplist:
def __init__(self):
self.head = Node(-float("inf"), MAX_LEVEL) # head sentinel node
self.level = 0 # current highest level in the list
# ------------- Helper -------------
def random_level(self):
"""Randomly decide the level of a new node (like flipping a coin)."""
lvl = 0
while random.random() < P_FACTOR and lvl < MAX_LEVEL:
lvl += 1
return lvl
# ------------- Search -------------
def search(self, target: int) -> bool:
"""
Start from top level and move right until next is >= target.
Then drop down one level. Repeat until level 0.
"""
curr = self.head
for i in range(self.level, -1, -1):
while curr.forward[i] and curr.forward[i].val < target:
curr = curr.forward[i]
curr = curr.forward[0]
return curr is not None and curr.val == target
# ------------- Insert -------------
def insert(self, num: int) -> None:
"""
1. Record nodes at each level just before where new node will go.
2. Randomly choose new node's height.
3. Insert and adjust forward pointers.
"""
update = [None] * (MAX_LEVEL + 1)
curr = self.head
# Step 1: move right/down to find insert position
for i in range(self.level, -1, -1):
while curr.forward[i] and curr.forward[i].val < num:
curr = curr.forward[i]
update[i] = curr
# Step 2: choose random level for new node
lvl = self.random_level()
# if new node is higher than current max, link head at new levels
if lvl > self.level:
for i in range(self.level + 1, lvl + 1):
update[i] = self.head
self.level = lvl
# Step 3: create and insert node
new_node = Node(num, lvl)
for i in range(lvl + 1):
new_node.forward[i] = update[i].forward[i]
update[i].forward[i] = new_node
# ------------- Erase -------------
def erase(self, num: int) -> bool:
"""
1. Like insert, find nodes before target at each level (update[]).
2. If found, reconnect pointers to skip target node.
3. Adjust top level if necessary.
"""
update = [None] * (MAX_LEVEL + 1)
curr = self.head
# Step 1: find predecessors
for i in range(self.level, -1, -1):
while curr.forward[i] and curr.forward[i].val < num:
curr = curr.forward[i]
update[i] = curr
curr = curr.forward[0]
if curr is None or curr.val != num:
return False # not found
# Step 2: unlink the target node
for i in range(self.level + 1):
if update[i].forward[i] != curr:
continue
update[i].forward[i] = curr.forward[i]
# Step 3: reduce list level if top level becomes empty
while self.level > 0 and self.head.forward[self.level] is None:
self.level -= 1
return True
4. Dry run
4.1. Search
Idea: Move right first -> Else go down, and find the exact node at the end.
- Start from head at top level (level 3).
-
Move right: 1 → 9 → stop (next 20 > 14).
-
Move down to level 2.
- At level 2:
-
From 9 → 14 ✅ found equal.
-
Move down to level 1 to confirm.
- At level 1:
- From 9 → 12 → 14 ✅ match found.
- At level 0:
- Move
Note: Using curr to detect and move => If you find 14 in level 2, 14 != 14 => Keep the curr and move forward to the next level
=> Also you can implement to return 14 if find in the level
Why we do not return 14 in high level when we found but need to travel until level 0 ?
- “Finding” at a higher level isn’t guaranteed correct position
4.2. Insert
Idea: Use updated array to find the curr value that we need to modify in each level.
Level 2: 1 ---- 5 ---- 9 ---- 18 ---- 20
Level 1: 1 - 3 - 5 - 7 - 9 - 12 - 17 - 18 - 20
Level 0: 1 2 3 4 5 6 7 8 9 10 11 12 13 17 18 19 20
| Level | Traverse path | Stop at node | Reason | update[i] |
| ----- | ---------------- | ------------ | -------------- | -------------- |
| 2 | head → 1 → 5 → 9 | 9 | next (18) > 14 | update[2] = 9 |
| 1 | start at 9 → 12 | 12 | next (17) > 14 | update[1] = 12 |
| 0 | start at 12 → 13 | 13 | next (17) > 14 | update[0] = 13 |
4.3. Delete
Idea: Use updated array to find the curr value that we need to modify in each level.
| Level | Traverse path | Stop at node | Reason | update[i] |
| ----- | ---------------- | ------------ | -------------- | -------------- |
| 2 | head → 1 → 5 → 9 | 9 | next (18) > 14 | update[2] = 9 |
| 1 | start at 9 → 12 | 12 | next (17) > 14 | update[1] = 12 |
| 0 | start at 12 → 13 | 13 | next (17) > 14 | update[0] = 13 |
5. Complexity
Operation Average Time Worst Time Space
Search O(log n) O(n) O(n)
Insert O(log n) O(n) O(n)
Erase O(log n) O(n) O(n)