Leetcode 1206 设计跳表
这道题提交的人数和题解特别少,我就稍微贡献一份力吧。
跳表是链表数据结构的一种拓展,能够在 时间复杂度下对一个有序链表进行访问(包括插入删除)。每一层上的节点通过一定概率向上构造一个节点,也就是 , 。 是人为定义的,为了方便起见,这里设置为 。
题干:
不使用任何库函数,设计一个跳表。
跳表是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。
例如,一个跳表包含 [30, 40, 50, 60, 70, 90],然后增加 80、45 到跳表中,以下图的方式操作:
跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(n)),空间复杂度是 O(n)。
在本题中,你的设计应该要包含这些函数:
bool search(int target) : 返回target是否存在于跳表中。
void add(int num): 插入一个元素到跳表。
bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回False. 如果存在多个 num ,删除其中任意一个即可,并输出True。
了解更多 : https://en.wikipedia.org/wiki/Skip_list
注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。
样例:
Skiplist skiplist = new Skiplist();
skiplist.add(1);
skiplist.add(2);
skiplist.add(3);
skiplist.search(0); // 返回 false
skiplist.add(4);
skiplist.search(1); // 返回 true
skiplist.erase(0); // 返回 false,0 不在跳表中
skiplist.erase(1); // 返回 true
skiplist.search(1); // 返回 false,1 已被擦除
约束条件:
0 <= num, target <= 20000
最多调用 50000 次 search, add, 以及 erase操作。
实现代码:
首先定义单个节点:包含值、右指针、下指针三个值。
# 引入几个全局变量
Maxlevel = 16
th = [pow(0.5, n) for n in range(Maxlevel, 0, -1)]
import random
# 构造单个节点
class Node(object):
def __init__(self, x = "inf"):
self.val = x
self.next = None
self.below = None
然后对整条链进行构造:因为不同层的头节点是始终存在的,因此将其通过一条向下的链,连接起来,这也就是我们的初始化步骤。
class Skiplist(object):
def __init__(self):
self.head = Node("inf")
cur = self.head
for i in range(0, Maxlevel):
cur.below = Node("inf")
cur = cur.below
补充代码:给定升序序列的列表,构造跳表construct()
;遍历跳表函数travel
:
从序列构造跳表construct()
这是我花时间花的最久的步骤,因为在构建时比较复杂,同时需要向下、向右构建链接。
遍历跳表 travel
相对简单一点,只需要逐层遍历即可。通过此,就可以完整的看到自己的链表实现的是否正确。
class Skiplist(object):
def __init__(self):
self.head = Node("inf")
cur = self.head
for i in range(0, Maxlevel):
cur.below = Node("inf")
cur = cur.below
def construct(self, lst):
if not lst:
return None
else:
# 初始化头节点
self.head = Node("inf")
cur = self.head
heads = [None] * Maxlevel
for i in range(0, Maxlevel):
cur.below = Node("inf")
# 记录下每层的头节点
heads[i] = cur
cur = cur.below
# 开始从上往下加入数字
curs = heads
for item in lst:
pr = random.random()
lvnum = 0
flag = 0
for i in range(0, Maxlevel):
if (i < Maxlevel - 1) & (pr > th[i]):
#记录在第几层
lvnum = lvnum + 1
else:
if flag == 0:
temp = i
flag = 1
curs[i].next = Node(item)
curs[i] = curs[i].next
# 添加向下的链接
for j in range(temp, Maxlevel - 1):
curs[j].below = curs[j+1]
return self.head
def travel(self):
lv = self.head
lvnum = 0
while lv:
cur = lv
print("Level", lvnum, end = ": ")
while cur:
print(cur.val, end = "->")
cur = cur.next
print("end")
lv = lv.below
if not lv.below:
break
lvnum = lvnum + 1
题目的要求:search()
,add()
,erase()
函数的实现:
为什么要写之前那个按列表构造的函数呢?答案来了,因为底下这些小函数用到的思路与那个的思路完全一致。
search()
函数
寻找,在每一层上找,如果找到了,输出True
;如果右边节点比target
大,那么如果本节点有下一层,就往下一层找,如果没有下一层,那就说明寻找完成,没有发现该节点,即条表中不存在该节点。
add()
函数
像 search()
函数一样,在每一层搜索到需要插入的位置,将所有的插入位置的指针写入一个列表curs
中。然后我们只需要对列表中的点进行操作就可以了:
- 先生成随机数,看需要从第几层开始插入。
- 如果当前指针没有后继
- 如果不需要插入,则往下走
- 如果需要插入,建立一个节点,建立起链接,并更新列表
curs
- 如果有后继指针,则如最简单的插入法,插入,并更新列表
curs
最后,建立向下的指针,这里需要注意:我们只能对更新过的指针建立向下链接。所以只需要判断当前这个指针的值是否为我们插入的值就可以了。
erase()
函数
先调用search()
函数,如果不在跳表中,直接输出False
,如果在条表中,如add()
函数中,先寻址,然后每层都删除就可。
函数代码如下:
def search(self, target):
"""
:type target: int
:rtype: bool
"""
cur = self.head
while cur:
while cur.next:
if cur.next.val > target:
break
elif cur.next.val == target:
return True
else:
cur = cur.next
cur = cur.below
return False
def add(self, num):
"""
:type num: int
:rtype: None
"""
cur = self.head
pos = []
while True:
if cur:
if cur.next:
if cur.next.val < num:
cur = cur.next
else:
pos.append(cur)
if cur.below:
cur = cur.below
else:
break
else:
pos.append(cur)
cur = cur.below
if not cur:
break
pr = random.random()
for i in range(0, Maxlevel):
if (i < Maxlevel - 1) & (pr > th[i]):
continue
else:
if pos[i].next == None:
pos[i].next = Node(num)
pos[i] = pos[i].next
else:
temp = pos[i].next
pos[i].next = Node(num)
pos[i] = pos[i].next
pos[i].next = temp
for i in range(0, Maxlevel-1):
if pos[i].val == num:
pos[i].below = pos[i+1]
def erase(self, num):
"""
:type num: int
:rtype: bool
"""
if not self.search(num):
return False
else:
cur = self.head
pos = []
while True:
if cur:
if cur.next:
if cur.next.val < num:
cur = cur.next
else:
pos.append(cur)
if cur.below:
cur = cur.below
else:
break
else:
pos.append(cur)
cur = cur.below
if not cur:
break
for i in range(0, Maxlevel):
if pos[i].next:
if pos[i].next.val != num:
continue
else:
pos[i].next = pos[i].next.next
return True
完整代码如下:(包括主函数)
Maxlevel = 16
th = [pow(0.5, n) for n in range(Maxlevel, 0, -1)]
import random
class Node(object):
def __init__(self, x = "inf"):
self.val = x
self.next = None
self.below = None
class Skiplist(object):
def __init__(self):
self.head = Node("inf")
cur = self.head
for i in range(0, Maxlevel):
cur.below = Node("inf")
cur = cur.below
def construct(self, lst):
if not lst:
return None
else:
# 初始化头节点
self.head = Node("inf")
cur = self.head
heads = [None] * Maxlevel
for i in range(0, Maxlevel):
cur.below = Node("inf")
# 记录下每层的头节点
heads[i] = cur
cur = cur.below
# 开始从上往下加入数字
curs = heads
for item in lst:
pr = random.random()
lvnum = 0
flag = 0
for i in range(0, Maxlevel):
if (i < Maxlevel - 1) & (pr > th[i]):
lvnum = lvnum + 1
else:
if flag == 0:
temp = i
flag = 1
curs[i].next = Node(item)
curs[i] = curs[i].next
# 添加向下的链接
for j in range(temp, Maxlevel - 1):
curs[j].below = curs[j+1]
return self.head
def travel(self):
lv = self.head
lvnum = 0
while lv:
cur = lv
print("Level", lvnum, end = ": ")
while cur:
print(cur.val, end = "->")
cur = cur.next
print("end")
lv = lv.below
if not lv.below:
break
lvnum = lvnum + 1
def search(self, target):
"""
:type target: int
:rtype: bool
"""
cur = self.head
while cur:
while cur.next:
if cur.next.val > target:
break
elif cur.next.val == target:
return True
else:
cur = cur.next
cur = cur.below
return False
def add(self, num):
"""
:type num: int
:rtype: None
"""
cur = self.head
pos = []
while True:
if cur:
if cur.next:
if cur.next.val < num:
cur = cur.next
else:
pos.append(cur)
if cur.below:
cur = cur.below
else:
break
else:
pos.append(cur)
cur = cur.below
if not cur:
break
pr = random.random()
for i in range(0, Maxlevel):
if (i < Maxlevel - 1) & (pr > th[i]):
continue
else:
if pos[i].next == None:
pos[i].next = Node(num)
pos[i] = pos[i].next
else:
temp = pos[i].next
pos[i].next = Node(num)
pos[i] = pos[i].next
pos[i].next = temp
for i in range(0, Maxlevel-1):
if pos[i].val == num:
pos[i].below = pos[i+1]
def erase(self, num):
"""
:type num: int
:rtype: bool
"""
if not self.search(num):
return False
else:
cur = self.head
pos = []
while True:
if cur:
if cur.next:
if cur.next.val < num:
cur = cur.next
else:
pos.append(cur)
if cur.below:
cur = cur.below
else:
break
else:
pos.append(cur)
cur = cur.below
if not cur:
break
for i in range(0, Maxlevel):
if pos[i].next:
if pos[i].next.val != num:
continue
else:
pos[i].next = pos[i].next.next
return True
# Your Skiplist object will be instantiated and called as such:
if __name__ == "__main__":
obj = Skiplist()
obj.add(1)
obj.add(2)
obj.add(3)
obj.search(0)
obj.add(4)
obj.search(1)
obj.erase(0)
obj.erase(1)
obj.search(1)
lst = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
obj.construct(lst)
target = 10
param_1 = obj.search(target)
print("param_1 =", param_1)
num = 65
obj.add(num)
param_2 = obj.search(num)
print("param_2 =", param_2)
param_3 = obj.erase(num)
obj.travel()
param_4 = obj.search(num)
print("param_4 =", param_4)
提交结果:
执行时间:392ms,超过53.33%
内存消耗:23.1MB,超过100%