设计跳表

77 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

1206. 设计跳表 - 力扣(LeetCode)

设计跳表

不使用任何库函数,设计一个 跳表

跳表 是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。

例如,一个跳表包含 [30, 40, 50, 60, 70, 90] ,然后增加 8045 到跳表中,以下图的方式操作:


Artyom Kalinin [CC BY-SA 3.0], via Wikimedia Commons

跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(n)),空间复杂度是 O(n)

了解更多 : en.wikipedia.org/wiki/Skip_l…

在本题中,你的设计应该要包含这些函数:

  • bool search(int target) : 返回target是否存在于跳表中。
  • void add(int num): 插入一个元素到跳表。
  • bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回false. 如果存在多个 num ,删除其中任意一个即可。

注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。

示例 1:

输入
["Skiplist", "add", "add", "add", "search", "add", "search", "erase", "erase", "search"]
[[], [1], [2], [3], [0], [4], [1], [0], [1], [1]]
输出
[null, null, null, null, false, null, true, false, true, false]
解释
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 <= 2 * 10^4
  • 调用search, add,  erase操作次数不大于 5 * 10^4

解题

const MAX_LEVEL = 16;
const LEVEL_P = 0.25;
function randomLevel() {
  let level = 0;
  while (Math.random() < LEVEL_P && level < MAX_LEVEL) {
    level++;
  }
  return level;
}

class SkipNode {
  constructor(val) {
    this.value = val;
    this.forward = [];
  }
}

class Skiplist {
  constructor() {
    this.head = new SkipNode(-1);
    this.level = 0;
  }
  
  /** 
   * @param {number} target
   * @return {boolean}
   */
  search(target) {
    let nodes = this.searchLeftNodes(target);
    let node = nodes[0].forward[0];
    return !!node && node.value === target;
  }
  
  /** 
   * @param {number} num
   * @return {void}
   */
  add(num) {
    const node = new SkipNode(num);
    let level = randomLevel();
    let nodes = this.searchLeftNodes(num);
    for (let i = 0; i <= level; i++) {
      let currNode = nodes[i] || this.head;
      node.forward[i] = currNode.forward[i];
      currNode.forward[i] = node;
    }
    return this;
  }
  
  /** 
   * @param {number} num
   * @return {boolean}
   */
  erase(num) {
    let nodes = this.searchLeftNodes(num);
    let i = 0
    for (; i < nodes.length; i++) {
      let node = nodes[i].forward[i];
      if (node && node.value === num) {
        nodes[i].forward[i] = node.forward[i];
      } else {
        break;
      }
    }
    return i!==0
  }
  searchLeftNodes(target) {
    let currNode = this.head;
    let nodes = new Array(currNode.forward.length).fill(null);

    let i = currNode.forward.length - 1;
    while (i >= 0) {
      let node = currNode.forward[i];
      if (!node || node.value >= target) {
        nodes[i] = currNode;
        i--;
      } else {
        currNode = node;
      }
    }
    return nodes;
  }
}

/**
 * Your Skiplist object will be instantiated and called as such:
 * var obj = new Skiplist()
 * var param_1 = obj.search(target)
 * obj.add(num)
 * var param_3 = obj.erase(num)
 */