DIY数据结构

178 阅读7分钟

复杂数据结构

实现一个并查集

并查集的实现比较简单,不过就是使用递归,涉及到路径压缩和权重对比等操作,比较简单

class BingchaSet {
  constructor(n) {
    this.rank = [];
    this.father = [];
    for (let i = 0; i < n; i++) {
      this.rank[i] = 1;
      this.father[i] = i;
    }
  }
  find(i) {
    if (this.father[i] === i) {
      return i;
    }
    const parent = this.find(this.father[i]);
    this.father[i] = parent;
    return parent;
  }
  union(i, j) {
    const parentI = this.find(i);
    const parentJ = this.find(j);
    if (this.rank[parentI] <= this.rank[parentJ]) {
      this.father[parentI] = parentJ;
    } else {
      this.father[parentJ] = parentI;
    }
    if (this.rank[parentI] === this.rank[parentJ] && parentI != parentI) {
      this.rank[parentJ]++;
    }
  }
}
export default BingchaSet;


684. 冗余连接

题目描述

在本问题中, 树指的是一个连通且无环的无向图。

输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。

返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。

例子1

输入: [[1,2], [1,3], [2,3]]
输出: [2,3]
解释: 给定的无向图为:
  1
 / \
2 - 3

例子2

输入: [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
解释: 给定的无向图为:
5 - 1 - 2
    |   |
    4 - 3


注意: 1 输入的二维数组大小在 3 到 1000。
2 二维数组中的整数在1到N之间,其中N是输入数组的大小。

思考

1 这里很明显使用并查集来解决,因为前面我们已经介绍了什么是并查集。

题目本身并不难,就是判断是否有环,可以使用并查集的find方法,如果发现两个节点的祖先都是同一个,那就是我们需要找的。

参考实现2

实现1

/**
 * @param {number[][]} edges
 * @return {number[]}
 */
import BingchaSet from "../bingchaSet/index";
// Runtime: 368 ms, faster than 5.94% of JavaScript online submissions for Redundant Connection.
// Memory Usage: 48.9 MB, less than 5.45% of JavaScript online submissions for Redundant Connection.
export default (edges) => {
  const len = edges.length;
  const bingset = new BingchaSet(len);
  for (let [u, v] of edges) {
    const x = bingset.find(u - 1);
    const y = bingset.find(v - 1);
    if (x !== y) {
      bingset.merge(u - 1, v - 1);
    } else {
      return [u, v];
    }
  }
};

146. LRU 缓存机制

题目描述

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。

实现 LRUCache 类:

LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

例子1

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

注意: 1 1 <= capacity <= 3000
2 0 <= key <= 3000
。 3 0 <= value <= 10^4
4 最多调用 3 * 10^4 次 get 和 put

思考

1 这里最难的就是如何找出最近最少使用的key值?

看了题解了解到,Map.keys()是按照set的顺序倒序输出的,这样就可以利用这一特性来进行解决寻找最近最少使用的key值

参考实现1

实现1

/**
 * @param {number} capacity
 */
var LRUCache = function (capacity) {
  this.map = new Map();
  this.max = capacity;
};

/**
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function (key) {
  console.log(this.map, this.keyMap);
  if (this.map.has(key)) {
    const val = this.map.get(key);
    this.map.delete(key);
    this.map.set(key, val);
    return val;
  } else {
    return -1;
  }
};

/**
 * @param {number} key
 * @param {number} value
 * @return {void}
 */
// Runtime: 196 ms, faster than 64.75% of JavaScript online submissions for LRU Cache.
// Memory Usage: 51.3 MB, less than 59.00% of JavaScript online submissions for LRU Cache.
LRUCache.prototype.put = function (key, value) {
  console.log(this.map.size, this.max);
  if (this.map.has(key)) {
    this.map.delete(key);
  }
  this.map.set(key, value);
  if (this.map.size > this.max) {
    const firstKey = this.map.keys().next().value;
    this.cache.delete(firstKey);
  }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * var obj = new LRUCache(capacity)
 * var param_1 = obj.get(key)
 * obj.put(key,value)
 */

716. 最大栈

题目描述

设计一个支持 push,pop,top,peekMax 和 popMax 的 stack 1 peekMax 获取栈中的最大元素
2 popMax 获取栈中的最大元素并且删除

例子1

MaxStack stack = new MaxStack();
stack.push(5); 
stack.push(1);
stack.push(5);
stack.top(); -> 5
stack.popMax(); -> 5
stack.top(); -> 1
stack.peekMax(); -> 5
stack.pop(); -> 1
stack.top(); -> 5

思考

1 使用两个栈,一个存储正常的push进来的数字,一个maxStack用来存储每次push进来的最大元素

参考实现1

实现1

class MaxStack {
  constructor() {
    this.normalStack = [];
    this.maxStack = [];
  }

  //O(1);
  push(val) {
    this.normalStack.push(val);
    const len = this.maxStack.length;
    if (len === 0 || (len > 0 && val >= this.maxStack[len - 1])) {
      this.maxStack.push(val);
    } else {
      this.maxStack.push(this.maxStack[len - 1]);
    }
  }
  //O(1);
  pop() {
    this.maxStack.pop();
    return this.normalStack.pop();
  }
  //O(1);
  // 获取当前最顶上的元素,但是不删除
  top() {
    if (this.normalStack.length > 0) {
      return this.normalStack[this.normalStack.length - 1];
    } else {
      return -1;
    }
  }
  //O(1);
  // 获取最大的stack中max元素
  peekMax() {
    if (this.maxStack.length > 0) {
      return this.maxStack[this.maxStack.length - 1];
    } else {
      return -1;
    }
  }
  //O(n);
  // 删除最大的元素
  popMax() {
    const res = this.peekMax();
    const temp = [];
    while (this.top() !== res) {
      const normalEle = this.normalStack.pop();
      temp.shift(normalEle);
      this.maxStack.pop();
    }
    this.pop();
    for (let val of temp) {
      this.push(val);
    }
    return res;
  }
}
export default MaxStack;


380. Insert Delete GetRandom O(1)

题目描述

设计一个插入、删除和随机取值均为 O(1) 时间复杂度的数据结构

例子1

输入:
["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
[[], [1], [2], [2], [], [1], [2], []]
输出:
[null, true, false, true, 2, true, false, 2]

思考

1 使用一个map就可以了

参考实现1

实现1

/**
 * Initialize your data structure here.
 */
var RandomizedSet = function () {
  this.map = new Map();
};

/**
 * Inserts a value to the set. Returns true if the set did not already contain the specified element.
 * @param {number} val
 * @return {boolean}
 */
RandomizedSet.prototype.insert = function (val) {
  if (!this.map.has(val)) {
    this.map.set(val, val);
    return true;
  } else {
    return false;
  }
};

/**
 * Removes a value from the set. Returns true if the set contained the specified element.
 * @param {number} val
 * @return {boolean}
 */
RandomizedSet.prototype.remove = function (val) {
  if (this.map.has(val)) {
    this.map.delete(val);
    return true;
  } else {
    return false;
  }
};

/**
 * Get a random element from the set.
 * @return {number}
 */
//  Runtime: 228 ms, faster than 24.73% of JavaScript online submissions for Insert Delete GetRandom O(1).
//  Memory Usage: 49.3 MB, less than 15.96% of JavaScript online submissions for Insert Delete GetRandom O(1).
RandomizedSet.prototype.getRandom = function () {
  const random = Math.floor(this.map.size * Math.random());
  let count = 0;
  for (let [key, val] of this.map) {
    if (count === random) {
      return val;
    } else {
      ++count;
    }
  }
};

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * var obj = new RandomizedSet()
 * var param_1 = obj.insert(val)
 * var param_2 = obj.remove(val)
 * var param_3 = obj.getRandom()
 */

432. 全 O(1) 的数据结构

题目描述

1 Inc(key) - 插入一个新的值为 1 的 key。或者使一个存在的 key 增加一,保证 key 不为空字符串。
2 Dec(key) - 如果这个 key 的值是 1,那么把他从数据结构中移除掉。否则使一个存在的 key 值减一。如果这个 key 不存在,这个函数不做任何事情。key 保证不为空字符串。
3 GetMaxKey() - 返回 key 中值最大的任意一个。如果没有元素存在,返回一个空字符串"" 。
4 GetMinKey() - 返回 key 中值最小的任意一个。如果没有元素存在,返回一个空字符串""。


思考

1 使用两个map,一个保存key对用的次数,一个保存次数对应的key值对象,然后通过一个双向链表链接起来,这样就可以达到取最大的key和最小的key都是O(1)

如果需要O(1), map和双向链表都是一种选择,

参考实现1

实现1

/**
 * Initialize your data structure here.
 */
class Bucket {
  constructor(val, preBucket, nextBucket) {
    this.count = val;
    this.next = nextBucket || null;
    this.pre = preBucket || null;
    this.keySet = new Set();
  }
}
var AllOne = function () {
  // key2count map
  this.key2countMap = new Map();
  // sameCount map, 每个count对应是一个双向的Bucket链表
  this.sameCountBucketMap = new Map();
  // this.head 指向最小的val
  this.head = new Bucket(Number.MIN_VALUE);
  // this.tail 指向最大的val
  this.tail = new Bucket(Number.MAX_VALUE);
  this.head.next = this.tail;
  this.tail.pre = this.head;
};

/**
 * Inserts a new key <Key> with value 1. Or increments an existing key by 1.
 * @param {string} key
 * @return {void}
 */
AllOne.prototype.inc = function (key) {
  // 如果存在,则把它加入到相同count的链表中去
  if (this.key2countMap.has(key)) {
    this.changeKey(key, 1);
  } else {
    this.key2countMap.set(key, 1);
    if (this.head.next.count !== 1) {
      this.addBucketAfter(new Bucket(1), this.head);
    }
    this.head.next.keySet.add(key);
    this.sameCountBucketMap.set(1, this.head.next);
  }
};

/**
 * Decrements an existing key by 1. If Key's value is 1, remove it from the data structure.
 * @param {string} key
 * @return {void}
 */
AllOne.prototype.dec = function (key) {
  if (this.key2countMap.has(key)) {
    const count = this.key2countMap.get(key);
    if (count === 1) {
      this.key2countMap.delete(key);
      this.removeKeyFromBucket(this.sameCountBucketMap.get(count), key);
    } else {
      this.changeKey(key, -1);
    }
  }
};

/**
 * Returns one of the keys with maximal value.
 * @return {string}
 */
AllOne.prototype.getMaxKey = function () {
  return this.tail.pre === this.head ? "" : this.tail.pre.keySet[Symbol.iterator]().next().value;
};

/**
 * Returns one of the keys with Minimal value.
 * @return {string}
 */
AllOne.prototype.getMinKey = function () {
  return this.head.next === this.tail ? "" : this.head.next.keySet[Symbol.iterator]().next().value;
};
// 把相同count的Bucket加入到sameCountBucketMap
AllOne.prototype.changeKey = function (key, offset) {
  // 首先修改原来存在key值
  const count = this.key2countMap.get(key);
  this.key2countMap.set(key, count + offset);
  const curBucket = this.sameCountBucketMap.get(count);
  let newBucket;
  if (this.sameCountBucketMap.has(count + offset)) {
    newBucket = this.sameCountBucketMap.get(count + offset);
  } else {
    // add new Bucket
    newBucket = new Bucket(count + offset);
    this.sameCountBucketMap.set(count + offset, newBucket);
    this.addBucketAfter(newBucket, offset === 1 ? curBucket : curBucket.pre);
  }
  newBucket.keySet.add(key);
  this.removeKeyFromBucket(curBucket, key);
};
AllOne.prototype.removeKeyFromBucket = function (bucket, key) {
  bucket.keySet.delete(key);
  if (bucket.keySet.size === 0) {
    this.removeBucketFromList(bucket);
    this.sameCountBucketMap.delete(bucket.count);
  }
};
AllOne.prototype.removeBucketFromList = function (bucket) {
  bucket.pre.next = bucket.next;
  bucket.next.pre = bucket.pre;
  bucket.next = null;
  bucket.pre = null;
};
AllOne.prototype.addBucketAfter = function (newBucket, preBucket) {
  newBucket.pre = preBucket;
  newBucket.next = preBucket.next;
  preBucket.next.pre = newBucket;
  preBucket.next = newBucket;
};
/**
 * Your AllOne object will be instantiated and called as such:
 * var obj = new AllOne()
 * obj.inc(key)
 * obj.dec(key)
 * var param_3 = obj.getMaxKey()
 * var param_4 = obj.getMinKey()
 */