51mee - AI智能招聘平台Logo
模拟面试题目大全招聘中心会员专区

假设你需要设计一个数据结构来支持以下操作:频繁的插入、删除和范围查询(如查询某个时间区间内的所有记录)。请说明你选择的数据结构(如跳表、平衡树、B+树等),并解释其时间复杂度。

微软Software Engineer Intern (Neurodiversity Hiring Program*)难度:中等

答案

1) 【一句话结论】

对于频繁插入、删除和范围查询的场景,推荐使用跳表,它能以O(log n)时间完成插入、删除和范围查询操作,实现相对简单,适合内存操作。

2) 【原理/概念讲解】

跳表是一种概率性平衡的多层链表,底层是普通有序链表,上层是“跳跃层”,通过随机提升节点到上层实现平衡。类比“城市立交桥”:底层道路覆盖所有节点(如普通链表),上层道路可快速跨越多个路口(如跳跃层),查询时从上层开始跳过大量节点,快速定位目标。

  • 核心逻辑:
    每个节点有多个next指针(对应不同层),上层节点只包含部分底层节点(如每隔一个节点提升到上层)。查询时从最高层开始,跳过大量节点,再逐层向下遍历,最终定位到目标。
  • 操作流程:
    • 插入:随机决定节点提升层数,从最高层开始调整指针,插入新节点。
    • 删除:调整相邻节点的指针,并递减跳表层数。
    • 范围查询:从最高层找到大于等于start的节点,再逐层向下遍历,收集所有key在[start, end]的节点。

3) 【对比与适用场景】

数据结构定义/特性时间复杂度(插入/删除/查询)适用场景注意点
跳表多层有序链表,概率性平衡O(log n)频繁插入删除、范围查询(内存操作)实现简单,空间开销较大
平衡二叉搜索树(如红黑树)自平衡二叉树,严格平衡O(log n)需严格平衡,范围查询实现复杂,性能稳定
B+树多叉树,叶子节点有序O(log n)(磁盘)磁盘存储,大规模数据需磁盘I/O,内存开销小

4) 【示例】

假设记录为(时间戳,值),操作包括插入、删除、范围查询。伪代码示例:

class Node:
    def __init__(self, key, value, level):
        self.key = key
        self.value = value
        self.next = [None] * level  # 多个next指针,对应不同层

class SkipList:
    def __init__(self):
        self.head = Node(-float('inf'), None, 1)  # 头节点
        self.level = 1  # 当前层数

    def random_level(self):
        level = 1
        while random.random() < 0.5 and level < 32:  # 随机层数,概率0.5
            level += 1
        return level

    def insert(self, key, value):
        level = self.random_level()
        new_node = Node(key, value, level)
        current = self.head
        for i in range(level, 0, -1):
            while current.next[i] and current.next[i].key < key:
                current = current.next[i]
            new_node.next[i] = current.next[i]
            current.next[i] = new_node
        if level > self.level:
            self.level = level

    def delete(self, key):
        current = self.head
        for i in range(self.level, 0, -1):
            while current.next[i] and current.next[i].key < key:
                current = current.next[i]
            if current.next[i] and current.next[i].key == key:
                current.next[i] = current.next[i].next[i]
        while self.level > 1 and not self.head.next[self.level]:
            self.level -= 1

    def range_query(self, start, end):
        result = []
        current = self.head
        for i in range(self.level, 0, -1):
            while current.next[i] and current.next[i].key < start:
                current = current.next[i]
        current = current.next[1]  # 从底层开始遍历
        while current and current.key <= end:
            result.append((current.key, current.value))
            current = current.next[1]
        return result

示例操作:

  • 插入(1,10)、(3,20),删除(2),查询[1,4]的记录,结果为(1,10)、(3,20)。

5) 【面试口播版答案】

面试官您好,对于频繁插入、删除和范围查询的场景,我会选择跳表。跳表是一种多层有序链表结构,底层是普通有序链表,上层是“跳跃层”,通过随机提升节点到上层实现概率性平衡。查询时从上层开始跳过大量节点,时间复杂度O(log n),插入删除时调整指针,同样O(log n)。相比平衡树,跳表实现更简单(无需维护颜色或平衡因子),适合内存操作。比如插入时,根据随机数决定节点是否提升,删除时调整相邻节点的指针,范围查询从最高层定位后,逐层向下遍历,最终返回区间内的所有记录。这样既能高效支持插入删除,又能快速完成范围查询,适合这类高频操作的场景。

6) 【追问清单】

  1. 跳表如何保证平衡?
    回答:通过随机选择节点提升层数(如概率0.5),每个节点有概率进入上层,整体保持近似对数级高度(期望高度为O(log n))。
  2. 跳表的空间复杂度如何?
    回答:每个节点有多个next指针(层数),空间开销比平衡树大(约1.44倍),但比B+树小,适合内存场景。
  3. 若数据量极大,跳表是否适合?
    回答:跳表适合内存操作,若数据量超出内存,可考虑B+树或分块跳表,但通常内存场景下跳表是优选项。
  4. 跳表的范围查询具体步骤?
    回答:从最高层找到大于等于start的节点,再从该节点开始逐层向下遍历,收集所有key在[start, end]的节点。
  5. 与红黑树的比较?
    回答:红黑树是严格平衡的,性能稳定,但实现复杂(需维护颜色和旋转);跳表实现简单,但性能波动较小(概率性),适合快速实现。

7) 【常见坑/雷区】

  1. 误认为跳表是严格平衡的,实际是概率性平衡,高度有波动。
  2. 忽略空间开销,如每个节点多个next指针导致空间复杂度较高。
  3. 混淆跳表与B+树,B+树适合磁盘存储,跳表适合内存。
  4. 范围查询时遗漏底层遍历,导致结果不完整。
  5. 插入时随机层数生成错误(如概率计算导致高度分布不均)。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1