Skip to main content

Design Skip List

SourceGist

package questions

import _utils.UseCommentAsDocumentation
import questions.common.ClassInvoker
import utils.shouldBe
import kotlin.random.Random

/**
* <img src="https://assets.leetcode.com/uploads/2019/09/27/1506_skiplist.gif" height="200" width="800"/>
*
* [Source](https://leetcode.com/problems/design-skiplist/) – [Gist](https://www.youtube.com/watch?v=UGaOXaXAM5M)
*/
@UseCommentAsDocumentation
class SkipList {
private val _headNode = Array<Node>(3) { level ->
Node(Int.MIN_VALUE).also {
it.level = level
}
}
private val random = Random(58)

private var array: Array<Node>

init {
for (i in 1.._headNode.lastIndex) {
_headNode[i].downRef = _headNode[i - 1]
}
array = Array(_headNode.size) {
_headNode[it]
}
}

fun search(target: Int): Boolean {
return search(array.last(), target)
}

private fun search(node: Node?, target: Int): Boolean {
if (node == null) {
return false
}
val parent = findParentInCurrentLevel(node, target)
if (parent.value == target) {
return true
}
if (parent.next != null) {
if (parent.next!!.value == target) {
return true
}
}
return search(parent.downRef, target)
}


/**
* Parent is the node with value less than [target]
* and the [Node.next] pointer points to a [Node] whose value is higher than [target]
*/
private fun findParentInCurrentLevel(node: Node, target: Int): Node {
var current: Node? = node
while (current != null) {
if (current.next == null) {
return current
} else if (current.value < target && current.next!!.value >= target) {
return current
}
current = current.next
}
return node

}


fun add(num: Int) {
val node = Node(num)
val level0Parent = findParentInCurrentLevel(array[0], num)
val next = level0Parent.next
// level 0 must contain the node
level0Parent.next = node
level0Parent.level = 0
node.next = next
var tempNode = node
for (i in 1..array.lastIndex) {
if (shouldAddUp()) {
val cloneNode = Node(num)
cloneNode.downRef = tempNode
cloneNode.level = i
val parent = findParentInCurrentLevel(array[i], num)
val next = parent.next
parent.next = cloneNode
cloneNode.next = next
tempNode = cloneNode
}
}
}

private fun shouldAddUp(): Boolean {
return random.nextBoolean()
}

fun erase(num: Int): Boolean {
val eraseList = mutableSetOf<Int>()
for (i in array.lastIndex downTo 0) {
if (i !in eraseList) {
erase(array[i], num, eraseList)
}
}
return eraseList.isNotEmpty()
}

private fun erase(node: Node?, target: Int, erasedList: MutableSet<Int>) {
if (node == null) {
return
}
val parent = findParentInCurrentLevel(node, target)
if (parent.next == null) {
// Not found in current level, so start from scratch in one level below
// erase(array.getOrNull(parent.level - 1), target, erasedList)
} else if (parent.next!!.value != target) {
// erase(array.getOrNull(parent.level - 1), target, erasedList)
} else if (parent.next!!.value == target) {
val nodeToBeRemoved = parent.next!!
erasedList.add(nodeToBeRemoved.level) // collect the levels at which the erasure happened
parent.next = parent.next?.next
}
}


inner class Node(val value: Int) {
var next: Node? = null
var downRef: Node? = null
var level: Int = 0

override fun toString(): String {
return "Node($value, lvl=${level}, next=${next?.value})"
}
}

}

fun main() {
SkipList().apply {
add(2)
add(3)
add(5)
add(9)
add(15)
add(0)
add(7)
search(15) shouldBe true
search(9) shouldBe true
search(2) shouldBe true
search(5) shouldBe true
search(0) shouldBe true
search(7) shouldBe true
search(11) shouldBe false
search(22) shouldBe false
search(33) shouldBe false
search(90) shouldBe false
add(40)
search(40) shouldBe true
erase(40) shouldBe true
search(40) shouldBe false
erase(7) shouldBe true
search(7) shouldBe false

}

SkipList().apply {
for (i in 0..100) {
add(i)
add(i)
add(i)
}
for (i in 1..50) {
search(i) shouldBe true
erase(i) shouldBe true
erase(i) shouldBe true
erase(i) shouldBe true
erase(i) shouldBe false
}
}

run {
val invoker = ClassInvoker<IntArray, Int>(
listOf(
"SkipList",
"add",
"add",
"add",
"add",
"add",
"erase",
"erase",
"add",
"search",
"search",
"add",
"erase",
"search",
"add",
"add",
"add",
"erase",
"search",
"erase",
"search",
"search",
"search",
"erase",
"erase",
"search",
"erase",
"add",
"add",
"erase",
"add",
"search",
"search",
"search",
"search",
"search"
)
) {
it.first()
}
invoker.invoke(
listOf(
intArrayOf(),
intArrayOf(9),
intArrayOf(4),
intArrayOf(5),
intArrayOf(6),
intArrayOf(9),
intArrayOf(2),
intArrayOf(1),
intArrayOf(2),
intArrayOf(7),
intArrayOf(4),
intArrayOf(5),
intArrayOf(6),
intArrayOf(5),
intArrayOf(6),
intArrayOf(7),
intArrayOf(4),
intArrayOf(3),
intArrayOf(6),
intArrayOf(3),
intArrayOf(4),
intArrayOf(3),
intArrayOf(8),
intArrayOf(7),
intArrayOf(6),
intArrayOf(7),
intArrayOf(4),
intArrayOf(1),
intArrayOf(6),
intArrayOf(3),
intArrayOf(4),
intArrayOf(7),
intArrayOf(6),
intArrayOf(1),
intArrayOf(0),
intArrayOf(3)

),
expectedAnswers = listOf(
null,
null,
null,
null,
null,
null,
false,
false,
null,
false,
true,
null,
true,
true,
null,
null,
null,
false,
true,
false,
true,
false,
false,
true,
true,
false,
true,
null,
null,
false,
null,
false,
true,
true,
false,
false
)
)
}
}

Updated on 2021-11-08