Facebook Pixel

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:

  1. Skiplist(): Initializes an empty skiplist object.

  2. search(int target): Returns true if the integer target exists in the skiplist, false otherwise.

  3. add(int num): Inserts the value num into the skiplist. The method should handle duplicate values.

  4. erase(int num): Removes one occurrence of the value num from the skiplist and returns true. If num doesn't exist in the skiplist, returns false without modifying the structure. When multiple instances of num 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. It keeps the structure balanced probabilistically without complex rebalancing operations
  2. It ensures O(log n) expected height since the probability of reaching level k is p^k
  3. 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 node
  • next: An array of pointers where next[i] points to the next node at level i

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 using find_closest
  • If i < level (node appears at this level), update pointers:
    • New node's next[i] points to current's next[i]
    • Current's next[i] points to the new node
  • 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 visit O(log n) levels and traverse O(1) nodes per level on average
  • Add: O(log n) expected - similar to search plus O(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 Evaluator

Example 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) → 37 → 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) → 35 -------→ None
Level 0: HEAD(-1) → 357 → 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) → 35 -----------→ None
Level 0: HEAD(-1) → 3557 → 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) → 357 → None

Key Observations:

  1. 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.

  2. Random Heights: Each node got a different random height (3: level 2, 7: level 1, 5: level 3), creating a balanced structure probabilistically.

  3. 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.

  4. Duplicate Handling: The second 5 was inserted normally, and when erasing, we removed only one occurrence.

  5. 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 probability p = 0.25, each node appears at higher levels, creating approximately log₁/ₚ(n) = log₄(n) levels on average. At each level, we traverse O(1/p) = O(4) nodes on average. Therefore, the total time complexity is O(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 in O(1) average time (geometric distribution with p = 0.25). The insertion process takes O(log n) time to find positions and O(1) to update pointers at each level, resulting in O(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 takes O(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 of 4/3 pointers per node, the total space for pointers is O(n × 4/3) = O(n).
  • The head node uses O(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):
    # ...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More