每日一题(202502)

0223 1206. 设计跳表

下面给出一个回答,包括题目大意、实现思路和写满注释的代码,每部分既不啰嗦也不简略:


1. 题目大意

设计一个跳表,要求在平均 O(log n) 时间内完成搜索、插入和删除操作。跳表由多层有序链表构成,每一层起到加速查找的作用,同时允许存在重复的值。


2. 实现思路

  • 跳表基本结构
    • 使用多层链表,每个节点保存一个数值和一个指针列表,指向每一层的下一个节点。
    • 最底层链表保存所有元素,上层链表是底层节点的“抽样”,用于快速跳跃搜索。
  • 随机层数生成
    • 插入新节点时,通过随机函数确定其层数,常用的方式是以一定概率(如 P_FACTOR=0.25)决定是否增加一层,最多不超过 MAX_LEVEL。
  • 各操作实现
    • 搜索:从最高层开始,沿当前层不断向右移动,直到下一个节点大于等于目标值,再降一层继续查找,直到最低层检查是否找到目标值。
    • 插入:首先在每一层记录最后一个小于待插入值的节点(update 数组),然后生成随机层数,并在相应层中更新指针将新节点插入。
    • 删除:类似于插入,先记录每层中最后一个小于待删除值的节点,确认目标节点存在后,在各层更新指针跳过目标节点,若删除后最高层为空则降低层数。

3. 写满注释的代码

from random import random

# 定义最大层数
MAX_LEVEL = 32
# 定义随机提升层数的概率因子
P_FACTOR = 0.25

def random_level() -> int:
    """
    随机生成新节点的层数。
    初始层数为1,每次随机值小于 P_FACTOR 则增加一层,
    但最多不超过 MAX_LEVEL。
    """
    lv = 1
    while lv < MAX_LEVEL and random() < P_FACTOR:
        lv += 1
    return lv

class SkiplistNode:
    """
    跳表节点类,包含节点的值和各层的指针列表(forward)。
    """
    __slots__ = 'val', 'forward'
    def __init__(self, val: int, max_level=MAX_LEVEL):
        # 节点的值
        self.val = val
        # forward[i] 表示该节点在第 i 层的下一个节点,初始化为 None
        self.forward = [None] * max_level

class Skiplist:
    """
    跳表类,包含搜索、插入、删除操作。
    """
    def __init__(self):
        # 创建头节点,头节点值设为 -1,头节点的 forward 列表长度为 MAX_LEVEL
        self.head = SkiplistNode(-1)
        # 当前跳表的有效层数,初始为 0 层
        self.level = 0

    def search(self, target: int) -> bool:
        """
        搜索跳表中是否存在目标值 target。
        从最高层开始,逐层向右遍历,找到每层中最后一个小于 target 的节点,
        最后检查最低层下一个节点是否等于 target。
        """
        curr = self.head
        # 从最高层到第 0 层依次遍历
        for i in range(self.level - 1, -1, -1):
            # 在当前层中向右移动,直到下一个节点的值不小于 target
            while curr.forward[i] and curr.forward[i].val < target:
                curr = curr.forward[i]
        # 到达最低层后,移动到下一个节点
        curr = curr.forward[0]
        # 如果节点存在且值等于 target,则返回 True,否则返回 False
        return curr is not None and curr.val == target

    def add(self, num: int) -> None:
        """
        在跳表中插入一个数值 num。
        先在每层记录最后一个小于 num 的节点,然后生成随机层数,
        并在相应层中更新指针将新节点插入跳表中。
        """
        # update 数组用于记录每一层最后一个小于 num 的节点
        update = [self.head] * MAX_LEVEL
        curr = self.head
        # 从最高层到第 0 层查找插入位置
        for i in range(self.level - 1, -1, -1):
            while curr.forward[i] and curr.forward[i].val < num:
                curr = curr.forward[i]
            update[i] = curr

        # 生成新节点的随机层数
        lv = random_level()
        # 更新当前跳表的有效层数
        self.level = max(self.level, lv)
        # 创建新节点,新节点只有 lv 层有效指针
        new_node = SkiplistNode(num, lv)
        # 在每一层更新指针,将新节点插入到链表中
        for i in range(lv):
            # 新节点的 forward 指向 update[i] 原先的下一个节点
            new_node.forward[i] = update[i].forward[i]
            # 将 update[i] 的 forward 指向新节点
            update[i].forward[i] = new_node

    def erase(self, num: int) -> bool:
        """
        在跳表中删除一个数值 num。如果存在多个相同的数值,仅删除其中一个。
        先在每层记录最后一个小于 num 的节点,然后确认目标节点存在后,
        在各层更新指针跳过目标节点,最后更新跳表的有效层数。
        """
        # update 数组记录每层最后一个小于 num 的节点
        update = [None] * MAX_LEVEL
        curr = self.head
        # 从最高层到第 0 层查找目标节点的前驱节点
        for i in range(self.level - 1, -1, -1):
            while curr.forward[i] and curr.forward[i].val < num:
                curr = curr.forward[i]
            update[i] = curr

        # 移动到第 0 层的下一个节点,即可能的目标节点
        curr = curr.forward[0]
        # 如果目标节点不存在或值不匹配,则返回 False
        if curr is None or curr.val != num:
            return False

        # 在每一层中更新指针,跳过目标节点
        for i in range(self.level):
            if update[i].forward[i] != curr:
                break
            update[i].forward[i] = curr.forward[i]

        # 更新跳表的当前有效层数:如果最高层已空,则降低层数
        while self.level > 1 and self.head.forward[self.level - 1] is None:
            self.level -= 1

        # 删除成功返回 True
        return True