1206. Design Skiplist
Problem Description
Design and implement a Skiplist data structure from scratch without using any built-in libraries.
A skiplist is a probabilistic data structure that provides O(log(n))
average time complexity for adding, erasing, and searching elements. It achieves similar performance to balanced tree structures like treaps and red-black trees but with simpler implementation logic based on linked lists.
The skiplist consists of multiple layers where:
- Each layer is a sorted linked list
- The bottom layer contains all elements
- Higher layers act as "express lanes" containing fewer elements, allowing faster traversal
- Elements are promoted to higher layers randomly, creating a hierarchical structure
You need to implement the Skiplist
class with the following methods:
-
Skiplist()
: Initializes an empty skiplist object. -
search(int target)
: Returnstrue
if the integertarget
exists in the skiplist,false
otherwise. -
add(int num)
: Inserts the valuenum
into the skiplist. The method should handle duplicate values. -
erase(int num)
: Removes one occurrence of the valuenum
from the skiplist and returnstrue
. Ifnum
doesn't exist in the skiplist, returnsfalse
without modifying the structure. When multiple instances ofnum
exist, removing any single instance is acceptable.
Important Note: The skiplist must support duplicate values - multiple instances of the same number can exist in the data structure.
The implementation uses a probabilistic approach where each new element is assigned a random height (number of levels it appears in). This randomization ensures the expected logarithmic time complexity for operations while maintaining the simplicity of the structure compared to self-balancing trees.
Intuition
Think of a skiplist like a multi-story building with express elevators. Instead of checking every floor (element) one by one, we can take express elevators that skip multiple floors, then switch to local elevators for fine-grained navigation.
The key insight is that we can speed up searching in a sorted linked list by adding "shortcuts" at higher levels. Imagine we have a sorted linked list at the bottom level containing all elements. If we had to search for element 80 in [30, 40, 50, 60, 70, 90]
, we'd need to traverse through all elements sequentially.
But what if we create another linked list above it that contains only some elements, say [30, 60, 90]
? Now we can first search in this sparse upper level - we quickly jump from 30 to 60, realize 80 is between them, then drop down to the lower level to search between 60 and 90. This reduces the number of comparisons.
We can extend this idea to multiple levels, where each higher level contains progressively fewer elements. The beauty is in how we decide which elements get promoted to higher levels - we use randomization. When inserting a new element, we flip a coin (with probability p = 0.25
) to decide if it should also appear in the next level up. This random promotion ensures that statistically, about 1/4 of elements reach level 2, 1/16 reach level 3, and so on.
The randomization is crucial because:
- It keeps the structure balanced probabilistically without complex rebalancing operations
- It ensures
O(log n)
expected height since the probability of reaching levelk
isp^k
- It simplifies insertion compared to deterministic balanced trees
For searching, we start from the highest level and move forward as far as possible without overshooting our target, then drop down a level and repeat. This "drop and continue" pattern naturally exploits the express lanes we've built.
The find_closest
helper method embodies this navigation strategy - at each level, it finds the node just before where our target would be, setting us up perfectly for either finding the target (search), inserting after it (add), or removing the next node if it matches (erase).
Learn more about Linked List patterns.
Solution Approach
The implementation uses two classes: Node
to represent skiplist nodes and Skiplist
for the main data structure.
Node Structure
Each Node
contains:
val
: The integer value stored in the nodenext
: An array of pointers wherenext[i]
points to the next node at leveli
The node's level determines how many layers it appears in. A node with level 3 will have pointers at levels 0, 1, and 2.
Skiplist Class Constants
max_level = 32
: Maximum possible levels in the skiplist (prevents unbounded growth)p = 0.25
: Probability of promoting a node to the next level (1/4 chance)
Core Components
1. Initialization
The skiplist starts with a sentinel head node with value -1
and max_level
pointers. The level
variable tracks the current maximum level being used (starts at 0).
2. Helper Method: find_closest(curr, level, target)
This method traverses horizontally at a given level to find the rightmost node whose value is less than target
. It's the workhorse for navigation:
while curr.next[level] and curr.next[level].val < target:
curr = curr.next[level]
3. Search Operation Starting from the highest active level, we:
- Use
find_closest
to get as close as possible to the target - Check if the next node at this level equals the target
- If not found, drop down a level and repeat
- Return
true
if found at any level,false
if we exhaust all levels
4. Add Operation
First, we randomly determine the new node's level using random_level()
:
level = 1 while level < max_level and random.random() < p: level += 1
Then, we traverse from the highest level downward:
- At each level
i
, find the insertion position usingfind_closest
- If
i < level
(node appears at this level), update pointers:- New node's
next[i]
points to current'snext[i]
- Current's
next[i]
points to the new node
- New node's
- Update the skiplist's maximum level if necessary
5. Erase Operation We search for the target value at each level:
- Use
find_closest
to position just before potential target - If the next node matches the target, bypass it by updating pointers
- Set
ok = True
to indicate successful deletion - After deletion, clean up empty top levels by reducing
self.level
The key pattern is the top-down traversal: we always start from the highest level and work our way down, using find_closest
to navigate horizontally before dropping levels. This ensures we take maximum advantage of the express lanes at higher levels.
Time Complexity Analysis
- Search:
O(log n)
expected - we visitO(log n)
levels and traverseO(1)
nodes per level on average - Add:
O(log n)
expected - similar to search plusO(1)
pointer updates per level - Erase:
O(log n)
expected - same traversal pattern as search with pointer updates
Space Complexity
O(n)
expected - each element appears in O(log n)
levels on average, giving n * O(log n) / n = O(n)
total space.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through building a skiplist and performing operations on it. We'll use p = 0.5
for this example (instead of 0.25) to make it easier to visualize.
Initial State:
Level 2: HEAD(-1) → None Level 1: HEAD(-1) → None Level 0: HEAD(-1) → None
Step 1: Add 3
random_level()
returns 2 (let's say we got lucky with coin flips)- Insert 3 at levels 0 and 1:
Level 2: HEAD(-1) → None Level 1: HEAD(-1) → 3 → None Level 0: HEAD(-1) → 3 → None
Step 2: Add 7
random_level()
returns 1 (only appears at level 0)- Start from level 1:
find_closest(HEAD, 1, 7)
returns HEAD (since 3 < 7) - At level 0: Insert 7 after 3
Level 2: HEAD(-1) → None Level 1: HEAD(-1) → 3 -------→ None Level 0: HEAD(-1) → 3 → 7 → None
Step 3: Add 5
random_level()
returns 3 (appears at levels 0, 1, and 2)- Start from level 2: Insert 5 after HEAD
- At level 1: Insert 5 after 3
- At level 0: Insert 5 between 3 and 7
Level 2: HEAD(-1) → 5 -----------→ None Level 1: HEAD(-1) → 3 → 5 -------→ None Level 0: HEAD(-1) → 3 → 5 → 7 → None
Step 4: Search for 7
- Start at level 2:
find_closest(HEAD, 2, 7)
returns HEAD- Next node is 5 (< 7), move to 5
- Next node is None, drop to level 1
- At level 1 (starting from node 5): Next is None, drop to level 0
- At level 0 (starting from node 5): Next is 7 - Found!
- Return
true
Step 5: Search for 6
- Start at level 2: Move to 5, then drop (5 < 6 < None)
- At level 1 (from node 5): Next is None, drop to level 0
- At level 0 (from node 5): Next is 7 (7 > 6), so 6 is not found
- Return
false
Step 6: Add 5 again (duplicate)
random_level()
returns 1- Navigate to find insertion points
- Insert the second 5 after the first 5 at level 0
Level 2: HEAD(-1) → 5 ----------------→ None Level 1: HEAD(-1) → 3 → 5 -----------→ None Level 0: HEAD(-1) → 3 → 5 → 5 → 7 → None
Step 7: Erase 5
- Start at level 2: Find 5, remove it by updating HEAD.next[2] = None
- At level 1: Find 5 after 3, remove it
- At level 0: Find first 5, remove it (leaving the second 5)
Level 2: HEAD(-1) → None Level 1: HEAD(-1) → 3 -----------→ None Level 0: HEAD(-1) → 3 → 5 → 7 → None
Key Observations:
-
Express Lane Effect: When searching for 7, we could skip directly from HEAD to 5 at level 2, avoiding nodes 3. This demonstrates the "express lane" concept.
-
Random Heights: Each node got a different random height (3: level 2, 7: level 1, 5: level 3), creating a balanced structure probabilistically.
-
Top-Down Navigation: We always start from the highest level and work our way down, moving forward as much as possible at each level before dropping.
-
Duplicate Handling: The second 5 was inserted normally, and when erasing, we removed only one occurrence.
-
Find Closest Pattern: At each level,
find_closest
positions us at the rightmost node less than our target, setting up perfect positioning for the next operation (search check, insertion point, or deletion candidate).
This walkthrough illustrates how the skiplist achieves logarithmic performance: higher levels let us skip large portions of the list, while lower levels provide fine-grained access to all elements.
Solution Implementation
1import random
2from typing import List, Optional
3
4
5class Node:
6 """Node class for skip list implementation."""
7 __slots__ = ['val', 'next']
8
9 def __init__(self, val: int, level: int) -> None:
10 """
11 Initialize a node with a value and level.
12
13 Args:
14 val: The value stored in the node
15 level: The number of forward pointers this node will have
16 """
17 self.val: int = val
18 self.next: List[Optional['Node']] = [None] * level
19
20
21class Skiplist:
22 """
23 Skip list data structure implementation.
24
25 A probabilistic data structure that allows O(log n) search, insertion and deletion.
26 Each node can have multiple forward pointers at different levels.
27 """
28
29 MAX_LEVEL: int = 32 # Maximum number of levels in the skip list
30 PROBABILITY: float = 0.25 # Probability for determining node levels
31
32 def __init__(self) -> None:
33 """Initialize an empty skip list with a sentinel head node."""
34 # Create head node with maximum possible level
35 self.head: Node = Node(-1, self.MAX_LEVEL)
36 # Current maximum level in use
37 self.level: int = 0
38
39 def search(self, target: int) -> bool:
40 """
41 Search for a target value in the skip list.
42
43 Args:
44 target: The value to search for
45
46 Returns:
47 True if the target exists, False otherwise
48 """
49 current: Node = self.head
50
51 # Start from the highest level and move down
52 for i in range(self.level - 1, -1, -1):
53 # Find the node just before where target would be at this level
54 current = self._find_closest_node(current, i, target)
55 # Check if the next node at this level contains the target
56 if current.next[i] and current.next[i].val == target:
57 return True
58
59 return False
60
61 def add(self, num: int) -> None:
62 """
63 Add a number to the skip list.
64
65 Args:
66 num: The number to add
67 """
68 current: Node = self.head
69 # Randomly determine the level for the new node
70 node_level: int = self._generate_random_level()
71 new_node: Node = Node(num, node_level)
72
73 # Update the maximum level if necessary
74 self.level = max(self.level, node_level)
75
76 # Insert the node at all levels from top to bottom
77 for i in range(self.level - 1, -1, -1):
78 # Find the position to insert at this level
79 current = self._find_closest_node(current, i, num)
80
81 # Only update pointers for levels within the new node's range
82 if i < node_level:
83 new_node.next[i] = current.next[i]
84 current.next[i] = new_node
85
86 def erase(self, num: int) -> bool:
87 """
88 Remove the first occurrence of a number from the skip list.
89
90 Args:
91 num: The number to remove
92
93 Returns:
94 True if the number was found and removed, False otherwise
95 """
96 current: Node = self.head
97 found: bool = False
98
99 # Remove the node from all levels where it appears
100 for i in range(self.level - 1, -1, -1):
101 # Find the node just before the target at this level
102 current = self._find_closest_node(current, i, num)
103
104 # If the next node contains the target value, remove it
105 if current.next[i] and current.next[i].val == num:
106 current.next[i] = current.next[i].next[i]
107 found = True
108
109 # Decrease the maximum level if top levels are now empty
110 while self.level > 1 and self.head.next[self.level - 1] is None:
111 self.level -= 1
112
113 return found
114
115 def _find_closest_node(self, current: Node, level: int, target: int) -> Node:
116 """
117 Find the rightmost node whose value is less than the target at a given level.
118
119 Args:
120 current: The starting node
121 level: The level to search at
122 target: The target value
123
124 Returns:
125 The node just before where the target would be positioned
126 """
127 # Move forward at this level while the next value is less than target
128 while current.next[level] and current.next[level].val < target:
129 current = current.next[level]
130
131 return current
132
133 def _generate_random_level(self) -> int:
134 """
135 Generate a random level for a new node using geometric distribution.
136
137 Returns:
138 A random level between 1 and MAX_LEVEL
139 """
140 level: int = 1
141
142 # Keep increasing level with probability PROBABILITY
143 while level < self.MAX_LEVEL and random.random() < self.PROBABILITY:
144 level += 1
145
146 return level
147
148
149# Your Skiplist object will be instantiated and called as such:
150# obj = Skiplist()
151# param_1 = obj.search(target)
152# obj.add(num)
153# param_3 = obj.erase(num)
154
1/**
2 * Implementation of a Skip List data structure.
3 * Skip List is a probabilistic data structure that allows O(log n) average time complexity
4 * for search, insertion, and deletion operations.
5 */
6class Skiplist {
7 // Maximum number of levels in the skip list
8 private static final int MAX_LEVEL = 32;
9
10 // Probability factor for determining node level (1/4 chance to go up each level)
11 private static final double PROBABILITY = 0.25;
12
13 // Random number generator for level assignment
14 private static final Random RANDOM = new Random();
15
16 // Sentinel head node with maximum level
17 private final Node head = new Node(-1, MAX_LEVEL);
18
19 // Current maximum level in use
20 private int currentLevel = 0;
21
22 /**
23 * Constructor for Skip List
24 */
25 public Skiplist() {
26 }
27
28 /**
29 * Searches for a target value in the skip list.
30 * @param target the value to search for
31 * @return true if target exists in the skip list, false otherwise
32 */
33 public boolean search(int target) {
34 Node current = head;
35
36 // Start from the highest level and move down
37 for (int i = currentLevel - 1; i >= 0; i--) {
38 // Find the closest node at current level that is less than target
39 current = findClosestNode(current, i, target);
40
41 // Check if the next node at this level contains the target
42 if (current.next[i] != null && current.next[i].value == target) {
43 return true;
44 }
45 }
46
47 return false;
48 }
49
50 /**
51 * Adds a number to the skip list.
52 * @param num the value to add
53 */
54 public void add(int num) {
55 Node current = head;
56
57 // Generate random level for the new node
58 int nodeLevel = generateRandomLevel();
59 Node newNode = new Node(num, nodeLevel);
60
61 // Update the current maximum level if necessary
62 currentLevel = Math.max(currentLevel, nodeLevel);
63
64 // Insert the node at each level from top to bottom
65 for (int i = currentLevel - 1; i >= 0; i--) {
66 // Find the position to insert at current level
67 current = findClosestNode(current, i, num);
68
69 // Only update pointers for levels within the new node's range
70 if (i < nodeLevel) {
71 newNode.next[i] = current.next[i];
72 current.next[i] = newNode;
73 }
74 }
75 }
76
77 /**
78 * Removes the first occurrence of a number from the skip list.
79 * @param num the value to remove
80 * @return true if the value was found and removed, false otherwise
81 */
82 public boolean erase(int num) {
83 Node current = head;
84 boolean found = false;
85
86 // Search and remove from each level
87 for (int i = currentLevel - 1; i >= 0; i--) {
88 // Find the node before the target at current level
89 current = findClosestNode(current, i, num);
90
91 // If next node contains the target value, remove it
92 if (current.next[i] != null && current.next[i].value == num) {
93 current.next[i] = current.next[i].next[i];
94 found = true;
95 }
96 }
97
98 // Decrease current level if top levels are empty
99 while (currentLevel > 1 && head.next[currentLevel - 1] == null) {
100 currentLevel--;
101 }
102
103 return found;
104 }
105
106 /**
107 * Finds the closest node whose value is less than the target at a specific level.
108 * @param startNode the node to start searching from
109 * @param level the level to search at
110 * @param target the target value
111 * @return the closest node whose value is less than target
112 */
113 private Node findClosestNode(Node startNode, int level, int target) {
114 Node current = startNode;
115
116 // Move forward at current level while next node's value is less than target
117 while (current.next[level] != null && current.next[level].value < target) {
118 current = current.next[level];
119 }
120
121 return current;
122 }
123
124 /**
125 * Generates a random level for a new node using geometric distribution.
126 * @return the randomly generated level (between 1 and MAX_LEVEL)
127 */
128 private static int generateRandomLevel() {
129 int level = 1;
130
131 // Keep increasing level with probability P until MAX_LEVEL is reached
132 while (level < MAX_LEVEL && RANDOM.nextDouble() < PROBABILITY) {
133 level++;
134 }
135
136 return level;
137 }
138
139 /**
140 * Node class representing an element in the skip list.
141 */
142 static class Node {
143 // The value stored in this node
144 int value;
145
146 // Array of references to next nodes at each level
147 Node[] next;
148
149 /**
150 * Constructor for Node.
151 * @param value the value to store
152 * @param level the number of levels for this node
153 */
154 Node(int value, int level) {
155 this.value = value;
156 this.next = new Node[level];
157 }
158 }
159}
160
161/**
162 * Your Skiplist object will be instantiated and called as such:
163 * Skiplist obj = new Skiplist();
164 * boolean param_1 = obj.search(target);
165 * obj.add(num);
166 * boolean param_3 = obj.erase(num);
167 */
168
1struct Node {
2 int val;
3 vector<Node*> next;
4
5 Node(int value, int level)
6 : val(value), next(level, nullptr) {}
7};
8
9class Skiplist {
10private:
11 static const int PROBABILITY_FACTOR = RAND_MAX / 4; // 25% probability for level promotion
12 static const int MAX_LEVEL = 32; // Maximum number of levels in skip list
13 Node* head; // Dummy head node
14 int currentLevel; // Current maximum level in use
15
16public:
17 Skiplist() {
18 head = new Node(-1, MAX_LEVEL);
19 currentLevel = 0;
20 }
21
22 bool search(int target) {
23 Node* current = head;
24
25 // Start from the highest level and move down
26 for (int i = currentLevel - 1; i >= 0; --i) {
27 // Find the closest node whose next value is less than target
28 current = findClosest(current, i, target);
29
30 // Check if the next node at this level contains the target
31 if (current->next[i] && current->next[i]->val == target) {
32 return true;
33 }
34 }
35
36 return false;
37 }
38
39 void add(int num) {
40 Node* current = head;
41 int newNodeLevel = randomLevel();
42 Node* newNode = new Node(num, newNodeLevel);
43
44 // Update the maximum level if necessary
45 currentLevel = max(currentLevel, newNodeLevel);
46
47 // Insert the new node at each level from top to bottom
48 for (int i = currentLevel - 1; i >= 0; --i) {
49 // Find the position to insert at level i
50 current = findClosest(current, i, num);
51
52 // Only insert at levels less than the new node's level
53 if (i < newNodeLevel) {
54 newNode->next[i] = current->next[i];
55 current->next[i] = newNode;
56 }
57 }
58 }
59
60 bool erase(int num) {
61 Node* current = head;
62 bool found = false;
63
64 // Search and remove the node at each level
65 for (int i = currentLevel - 1; i >= 0; --i) {
66 current = findClosest(current, i, num);
67
68 // If found at this level, skip over it
69 if (current->next[i] && current->next[i]->val == num) {
70 current->next[i] = current->next[i]->next[i];
71 found = true;
72 }
73 }
74
75 // Decrease the current level if top levels are empty
76 while (currentLevel > 1 && !head->next[currentLevel - 1]) {
77 --currentLevel;
78 }
79
80 return found;
81 }
82
83private:
84 // Find the rightmost node at given level whose value is less than target
85 Node* findClosest(Node* current, int level, int target) {
86 while (current->next[level] && current->next[level]->val < target) {
87 current = current->next[level];
88 }
89 return current;
90 }
91
92 // Generate a random level for a new node (geometric distribution)
93 int randomLevel() {
94 int level = 1;
95
96 // Keep increasing level with 25% probability
97 while (level < MAX_LEVEL && rand() < PROBABILITY_FACTOR) {
98 ++level;
99 }
100
101 return level;
102 }
103};
104
105/**
106 * Your Skiplist object will be instantiated and called as such:
107 * Skiplist* obj = new Skiplist();
108 * bool param_1 = obj->search(target);
109 * obj->add(num);
110 * bool param_3 = obj->erase(num);
111 */
112
1// Node structure for skip list
2interface SkipNode {
3 val: number;
4 next: (SkipNode | null)[];
5}
6
7// Constants for skip list configuration
8const MAX_LEVEL = 32;
9const PROBABILITY = 0.25;
10
11// Global variables for skip list state
12let head: SkipNode;
13let currentLevel: number;
14
15/**
16 * Initialize the skip list with a sentinel head node
17 */
18function initialize(): void {
19 head = {
20 val: -1,
21 next: Array(MAX_LEVEL).fill(null)
22 };
23 currentLevel = 0;
24}
25
26/**
27 * Search for a target value in the skip list
28 * @param target - The value to search for
29 * @returns true if the value exists, false otherwise
30 */
31function search(target: number): boolean {
32 let current = head;
33
34 // Start from the highest level and move down
35 for (let i = currentLevel - 1; i >= 0; i--) {
36 // Find the closest node at current level
37 current = findClosestNode(current, i, target);
38
39 // Check if the next node at this level contains the target
40 if (current.next[i] && current.next[i]!.val === target) {
41 return true;
42 }
43 }
44
45 return false;
46}
47
48/**
49 * Add a number to the skip list
50 * @param num - The number to add
51 */
52function add(num: number): void {
53 let current = head;
54
55 // Determine the level for the new node randomly
56 const nodeLevel = generateRandomLevel();
57
58 // Create new node with the determined level
59 const newNode: SkipNode = {
60 val: num,
61 next: Array(nodeLevel).fill(null)
62 };
63
64 // Update the global level if necessary
65 currentLevel = Math.max(currentLevel, nodeLevel);
66
67 // Insert the node at each level from top to bottom
68 for (let i = currentLevel - 1; i >= 0; i--) {
69 // Find the position to insert at current level
70 current = findClosestNode(current, i, num);
71
72 // Only update links for levels within the new node's height
73 if (i < nodeLevel) {
74 newNode.next[i] = current.next[i];
75 current.next[i] = newNode;
76 }
77 }
78}
79
80/**
81 * Remove a number from the skip list
82 * @param num - The number to remove
83 * @returns true if the number was found and removed, false otherwise
84 */
85function erase(num: number): boolean {
86 let current = head;
87 let found = false;
88
89 // Search and remove from each level
90 for (let i = currentLevel - 1; i >= 0; i--) {
91 // Find the node just before the target
92 current = findClosestNode(current, i, num);
93
94 // If the next node contains the target value, remove it
95 if (current.next[i] && current.next[i]!.val === num) {
96 current.next[i] = current.next[i]!.next[i];
97 found = true;
98 }
99 }
100
101 // Adjust the global level if top levels are empty
102 while (currentLevel > 1 && head.next[currentLevel - 1] === null) {
103 currentLevel--;
104 }
105
106 return found;
107}
108
109/**
110 * Find the closest node whose value is less than the target at a specific level
111 * @param node - Starting node for the search
112 * @param level - The level to search at
113 * @param target - The target value to compare against
114 * @returns The closest node whose value is less than target
115 */
116function findClosestNode(node: SkipNode, level: number, target: number): SkipNode {
117 // Traverse forward at the current level while values are less than target
118 while (node.next[level] && node.next[level]!.val < target) {
119 node = node.next[level]!;
120 }
121 return node;
122}
123
124/**
125 * Generate a random level for a new node using geometric distribution
126 * @returns A random level between 1 and MAX_LEVEL
127 */
128function generateRandomLevel(): number {
129 let level = 1;
130
131 // Keep increasing level with probability p
132 while (level < MAX_LEVEL && Math.random() < PROBABILITY) {
133 level++;
134 }
135
136 return level;
137}
138
139// Initialize the skip list on module load
140initialize();
141
Time and Space Complexity
Time Complexity:
The skip list operations (search
, add
, and erase
) all have an average time complexity of O(log n)
, where n
is the number of nodes in the skip list.
-
Search Operation: Starting from the highest level, we traverse down through the levels. At each level, we move forward using
find_closest
until we find a node whose next element is greater than or equal to the target. With probabilityp = 0.25
, each node appears at higher levels, creating approximatelylog₁/ₚ(n) = log₄(n)
levels on average. At each level, we traverseO(1/p) = O(4)
nodes on average. Therefore, the total time complexity isO(log n) × O(1) = O(log n)
. -
Add Operation: Similar to search, we traverse from the highest level to find the correct insertion positions at each level. The
random_level()
function runs inO(1)
average time (geometric distribution withp = 0.25
). The insertion process takesO(log n)
time to find positions andO(1)
to update pointers at each level, resulting inO(log n)
total time. -
Erase Operation: We traverse all levels from top to bottom to find and remove the target node, which takes
O(log n)
time. The additional cleanup to reduce the skip list level when the top levels become empty takesO(log n)
in the worst case.
Space Complexity:
The space complexity is O(n)
, where n
is the number of nodes in the skip list.
- Each node is created with a random level determined by
random_level()
. The expected number of pointers per node follows a geometric distribution:1 + p + p² + p³ + ... = 1/(1-p) = 1/(1-0.25) = 4/3
. - With
n
nodes and an average of4/3
pointers per node, the total space for pointers isO(n × 4/3) = O(n)
. - The
head
node usesO(max_level) = O(32) = O(1)
space. - Therefore, the overall space complexity is
O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Level Management During Deletion
One of the most common mistakes is failing to properly update the skiplist's maximum level after deletion operations. When we delete nodes, the top levels might become empty, but if we don't adjust self.level
, future operations will unnecessarily traverse these empty levels, degrading performance.
Problematic Code:
def erase(self, num: int) -> bool:
current = self.head
found = False
for i in range(self.level - 1, -1, -1):
current = self._find_closest_node(current, i, num)
if current.next[i] and current.next[i].val == num:
current.next[i] = current.next[i].next[i]
found = True
# Forgot to clean up empty levels!
return found
Solution: Always check and reduce the maximum level after deletion:
# After deletion, clean up empty top levels
while self.level > 1 and self.head.next[self.level - 1] is None:
self.level -= 1
2. Deleting All Occurrences Instead of One
The problem specifically asks to delete only ONE occurrence when duplicates exist, but it's easy to accidentally delete all matching nodes at once.
Problematic Code:
def erase(self, num: int) -> bool:
current = self.head
found = False
for i in range(self.level - 1, -1, -1):
current = self._find_closest_node(current, i, num)
# This continues deleting even after finding the first match
if current.next[i] and current.next[i].val == num:
current.next[i] = current.next[i].next[i]
found = True
Solution: Track which specific node to delete across all levels:
def erase(self, num: int) -> bool:
current = self.head
target_node = None
# First, find the node to delete
for i in range(self.level - 1, -1, -1):
current = self._find_closest_node(current, i, num)
if current.next[i] and current.next[i].val == num:
if target_node is None:
target_node = current.next[i]
# Only delete if it's the same node instance
if current.next[i] is target_node:
current.next[i] = current.next[i].next[i]
# Clean up levels...
return target_node is not None
3. Off-by-One Errors in Level Indexing
Confusion between level count and level indices is common. If level = 3
, the valid indices are 0, 1, and 2, not 1, 2, and 3.
Problematic Code:
def __init__(self):
self.head = Node(-1, self.MAX_LEVEL)
self.level = 1 # Wrong! Should start at 0 for empty list
def add(self, num):
node_level = self._generate_random_level()
# Wrong range - should be (self.level - 1, -1, -1)
for i in range(self.level, 0, -1):
# This will skip level 0 and might access invalid indices
Solution:
- Initialize
self.level = 0
for an empty skiplist - Always use
range(self.level - 1, -1, -1)
to iterate from highest to lowest level - Remember that a node with
level = k
has indices from 0 to k-1
4. Not Handling the Case When Random Level Exceeds Current Maximum
When adding a node with a level higher than the current maximum, we must update self.level
BEFORE traversing levels.
Problematic Code:
def add(self, num):
node_level = self._generate_random_level()
new_node = Node(num, node_level)
# Using old self.level value - won't connect higher levels!
for i in range(self.level - 1, -1, -1):
# If node_level > self.level, higher levels won't be connected
if i < node_level:
new_node.next[i] = current.next[i]
current.next[i] = new_node
# Too late to update here
self.level = max(self.level, node_level)
Solution: Update the maximum level before the traversal:
self.level = max(self.level, node_level)
# Now traverse using the updated level
for i in range(self.level - 1, -1, -1):
# ...
Which data structure is used to implement recursion?
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!