Design a Skiplist without using any built-in libraries.
A skiplist is a data structure that takes O(log(n))
time to add, erase, and search. Comparing with treap and red-black tree which has the same function and performance, the code length of Skiplist can be comparatively short and the idea behind Skiplists is just simple linked lists.
For example, we have a Skiplist containing [30,40,50,60,70,90]
and we want to add 80
and 45
into it.
Implement the Skiplist
class:
Skiplist()
Initializes the object of the skiplist.bool search(int target)
Returns true
if the integer target
exists in the Skiplist or false
otherwise.void add(int num)
Inserts the value num
into the SkipList.bool erase(int num)
Removes the value num
from the Skiplist and returns true
. If num
does not exist in the Skiplist, do nothing and return false
. If there exist multiple num
values, removing any one of them is fine.Note that duplicates may exist in the Skiplist, your code needs to handle this situation.
Example:
Input
["Skiplist", "add", "add", "add", "search", "add", "search", "erase", "erase", "search"]
[[], [1], [2], [3], [0], [4], [1], [0], [1], [1]]
Output
[null, null, null, null, false, null, true, false, true, false]
Explanation
Skiplist skiplist = new Skiplist();
skiplist.add(1);
skiplist.add(2);
skiplist.add(3);
skiplist.search(0); // return False
skiplist.add(4);
skiplist.search(1); // return True
skiplist.erase(0); // return False, 0 is not in skiplist.
skiplist.erase(1); // return True
skiplist.search(1); // return False, 1 has already been erased.
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
Imagine a skiplist as a simple sorted list where we need to quickly find, add, or remove items. The most basic approach is to completely ignore any advanced data structures and just work with a regular sorted list. Every operation then involves going through the list to find the right spot.
Here's how the algorithm would work step-by-step:
class Skiplist:
def __init__(self):
self.sorted_list = []
def search(self, target_value: int) -> bool:
for element in self.sorted_list:
if element == target_value:
return True
return False
def add(self, num: int) -> None:
# Find the correct position to insert the new number.
insertion_index = 0
while insertion_index < len(self.sorted_list) and self.sorted_list[insertion_index] < num:
insertion_index += 1
# Insert the new number at the found position.
self.sorted_list.insert(insertion_index, num)
def erase(self, num: int) -> bool:
# Iterate through the list to find the element to remove.
for index, element in enumerate(self.sorted_list):
if element == num:
# Remove the element if found.
del self.sorted_list[index]
return True
# If the element is not found, return False.
return False
Imagine creating express lanes on a road. This lets you skip over sections quickly. We'll use a similar idea to quickly find and modify elements in a sorted list, using multiple levels of 'express lanes'.
Here's how the algorithm would work step-by-step:
import random
class SkiplistNode:
def __init__(self, value, level):
self.value = value
self.forward = [None] * (level + 1)
class Skiplist:
def __init__(self, max_level=16, probability=0.5):
self.max_level = max_level
self.probability = probability
self.level = 0
self.head = SkiplistNode(-1, max_level)
def _random_level(self):
level = 0
while random.random() < self.probability and level < self.max_level:
level += 1
return level
def search(self, target_value):
node = self.head
for i in range(self.level, -1, -1):
while node.forward[i] and node.forward[i].value < target_value:
node = node.forward[i]
node = node.forward[0]
return node is not None and node.value == target_value
def add(self, number):
update = [None] * (self.max_level + 1)
node = self.head
for i in range(self.level, -1, -1):
while node.forward[i] and node.forward[i].value < number:
node = node.forward[i]
update[i] = node
level = self._random_level()
# Adjust the skiplist level if needed
if level > self.level:
for i in range(self.level + 1, level + 1):
update[i] = self.head
self.level = level
new_node = SkiplistNode(number, level)
# Update pointers for the new node
for i in range(level + 1):
new_node.forward[i] = update[i].forward[i]
update[i].forward[i] = new_node
def erase(self, number):
update = [None] * (self.max_level + 1)
node = self.head
for i in range(self.level, -1, -1):
while node.forward[i] and node.forward[i].value < number:
node = node.forward[i]
update[i] = node
node = node.forward[0]
# Only proceed if the node to delete exists
if node is not None and node.value == number:
# Update pointers to remove the node
for i in range(self.level + 1):
if update[i].forward[i] != node:
break
update[i].forward[i] = node.forward[i]
# Adjust skiplist level if topmost levels are empty
while self.level > 0 and self.head.forward[self.level] is None:
self.level -= 1
Case | How to Handle |
---|---|
Inserting the same element multiple times | The skiplist should allow duplicate values, so multiple insertions of the same value should be handled correctly by inserting nodes in the appropriate positions. |
Deleting a non-existent element | The delete operation should gracefully handle the case where the element is not found in the skiplist, without throwing an error, and return false. |
Maximum number of elements in skiplist approaching memory limits | The skiplist should be designed to minimize memory footprint and be aware of potential memory constraints, potentially adjusting the probability to manage memory use. |
Very large or very small values being inserted (Integer overflow or underflow during calculations) | Use appropriate data types (e.g., long) and consider the potential for overflow during comparisons or level generation, potentially scaling down values. |
Probability p is 0 or 1 | Handle these cases by throwing an exception or clamping p to a valid range (0 < p < 1). |
Searching for element smaller than the minimum element | The search operation should return null or a value indicating that the element is not found. |
Deleting the minimum or maximum element in the skiplist | Ensure the delete operation correctly updates the head pointers of the affected levels when removing the minimum or maximum element. |
Concurrency: multiple threads inserting, deleting, or searching simultaneously | If multi-threading is considered, appropriate locking mechanisms or concurrent data structures should be used to ensure thread safety. |