前端数据结构总结
栈
栈是什么
- 一种后进先出的数据结构
- js 没有栈数据结构,但是可以用 Array 实现
- push(入栈)、pop(出栈)、stack[stack.length - 1](栈顶元素)
const stack = [];
stack.push(1);
stack.push(2);
const item1 = stack.pop();
const item2 = stack.pop();
栈使用场景
- 需要后进先出的场景
- 比如:十进制转二进制、判断字符串的括号是否有效、函数调用堆栈等
一、十进制转二进制
- 后出的余数反而要排到前面
- 把余数依次入栈再出栈,就可以得到二进制结果
二、括号闭合
- 左括号入栈,遇到右括号出栈
- 最后栈空则合法
(((((()))))) // Valid
()()()() // Valid
((((((()) // Invalid
(()((()))) // Valid
三、函数调用堆栈
- 最后调用的函数最先执行完
- JS 解释器用栈控制函数的调用顺序
function foo1() {
...
foo2();
...
}
function foo2() {
console.log(1);
}
foo1();
leetcode 相关题目
leetcode-20.有效括号
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function (s) {
// 长度奇数直接返回false
if (s.length % 2 !== 0) return false;
const stack = [];
const m = new Map();
m.set('(', ')');
m.set('[', ']');
m.set('{', '}');
// 开头右括号直接返回false
if (!m.has(s[0])) return false;
// 循环
for (let i = 0; i < s.length; i++) {
if (m.has(s[i])) {
stack.push(s[i]);
} else {
const top = stack[stack.length - 1];
if (m.get(top) === s[i]) {
stack.pop();
} else {
return false;
}
}
}
return stack.length === 0;
};
leetcode-144.二叉树前序遍历
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function (root) {
const res = [];
const stack = [];
if (root) stack.push(root);
while (stack.length) {
const top = stack.pop();
res.push(top.val);
if (top.right) stack.push(top.right);
if (top.left) stack.push(top.left);
}
return res;
};
队列
队列是什么
- 一种先进先出的数据结构
- js 没有栈数据结构,但是可以用 Array 实现
- push(入队)、shift(出队)、queue[0](队列第一个顶元素)
const queue = [];
queue.push(1);
queue.push(2);
const item1 = queue.shift();
const item2 = queue.shift();
队列使用场景
- 需要先进先出的场景
- 比如:食堂排队打饭、JS 异步中的任务队列、计算最近请求次数
一、JS 异步中的任务队列
- JS 单线程,无法处理异步中的并发任务
- 使用任务队列先后处理异步任务
leetcode 相关题目
leetcode-933.最近请求次数
/**
* @param {number} t
* @return {number}
*/
RecentCounter.prototype.ping = function (t) {
this.q.push(t);
while (this.q[0] < t - 3000) {
this.q.shift();
}
return this.q.length;
};
链表
链表是什么
- 多个元素组成的列表
- 元素储存不连续,用 next 指针连接在一起
- js 中没有链表,可以用 Object 实现
数组 vs 链表
- 数组:增删非首尾元素时往往需要移动元素
- 比如:增删非首尾元素时,只需修改 next 指向即可,不需要移动元素
const a = { val: 'a' };
const b = { val: 'b' };
const c = { val: 'c' };
const d = { val: 'd' };
a.next = b;
b.next = c;
c.next = d;
// 遍历链表
let p = a;
while (p) {
console.log(p);
p = p.next;
}
// 插入
const e = { val: 'e' };
c.next = e;
e.next = d;
// 删除
c.next = d; // e 在链表中被删除
JS 中的原型链与链表
- 原型链本质是链表结构
- 原型链上的节点是各种原型对象,如 Object.prototype 等
- 原型链通过 __proto__ 属性连接各原型对象
// 手写 instanceof
const instanceof = (a, b) => {
let p = a;
while (p) {
if (p.__proto__ === b.prototype) {
return true;
}
p = p.__proto__;
}
return false;
}
const o = {};
const foo = () => {};
Object.prototype.a = 'a';
Function.prototype.b = 'b';
console.log(o.a); // 'a'
console.log(o.b); // undefined
console.log(foo.a); // 'a'
console.log(foo.b); // 'b'
JS 使用链表指针获取 json 节点值
const json = {
a: { b: { c: 1 } },
d: { e: 2 }
};
const path = ['a', 'b', 'c'];
let p = json;
path.forEach(k => {
p = p[k];
})
return p;
leetcode 相关题目
leetcode-237.删除链表中的节点
/**
* @param {ListNode} node
* @return {void} Do not return anything, modify node in-place instead.
*/
var deleteNode = function (node) {
node.val = node.next.val;
node.next = node.next.next;
};
leetcode-206.反转链表
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function (head) {
// 双指针遍历
let p1 = head;
let p2 = null;
while (p1) {
const tmp = p1.next;
p1.next = p2;
p2 = p1;
p1 = tmp;
}
return p2
};
leetcode-2.两数相加
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function (l1, l2) {
const l3 = new ListNode();
let p1 = l1;
let p2 = l2;
let p3 = l3;
let carry = 0;
while (p1 || p2) {
const v1 = p1 ? p1.val : 0;
const v2 = p2 ? p2.val : 0;
const sum = v1 + v2 + carry;
carry = Math.floor(sum / 10);
p3.next = new ListNode(sum % 10);
if (p1) p1 = p1.next;
if (p2) p2 = p2.next;
p3 = p3.next;
}
if (carry) {
p3.next = new ListNode(carry);
}
return l3.next
};
leetcode-83.删除排序链表中的重复元素
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function (head) {
let p = head;
while (p && p.next) {
if (p.val === p.next.val) {
p.next = p.next.next;
} else {
p = p.next;
}
}
return head;
};
leetcode-141.环形链表
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function (head) {
let p1 = head;
let p2 = head;
while (p1 && p2 && p2.next) {
p1 = p1.next;
p2 = p2.next.next;
if (p1 === p2) return true
}
return false;
};
leetcode-234.回文链表
/**
* @param {ListNode} head
* @return {boolean}
*/
var isPalindrome = function (head) {
let fp = head;
let sp = head;
let reverse = null;
while (fp && fp.next && sp) {
// 快慢指针 + 翻转链表
fp = fp.next.next;
const tmp = sp.next;
sp.next = reverse;
reverse = sp;
sp = tmp;
}
if (fp) {
// 节点个数为奇数,sp 需再走一步
sp = sp.next;
}
while (sp && reverse) {
if (sp.val !== reverse.val) {
return false;
}
sp = sp.next;
reverse = reverse.next;
}
return true;
};
集合
集合是什么
- 一种无序且唯一的数据结构
- ES6 中有集合,Set
- 常用操作:去重、判断是否在集合中、求交集
// 去重
const arr = [1, 1, 2, 2];
const arr2 = [...new Set(arr)];
// 判断是否在集合中
const set = new Set(arr);
set.has(2); // true
// 求交集
const set2 = new Set([2, 3]);
const set3 = new Set([...set1].filter(item => set2.has(item)));
// add、delete、has、迭代
set.add(2);
set.delete(2);
set.has(2);
set.forEach(val => console.log(val));
// set -> array
const arr1 = [...set1];
const arr2 = Array.from(set2);
// array -> set
const set1 = new Set(arr1);
leetcode 相关题目
leetcode-349.两个数组的交集
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersection = function (nums1, nums2) {
// 求交集,无序且唯一,使用集合
// 时间复杂度 O(n2)
// const set1 = new Set(nums1);
// const set2 = new Set(nums2);
// const set3 = new Set([...set1].filter(item => set2.has(item)));
// return [...set3];
// 用字典
// 时间复杂度 O(2n)
const m = new Map();
const res = [];
nums1.forEach(item => {
m.set(item, true);
})
nums2.forEach(item => {
if (m.get(item)) {
res.push(item);
m.delete(item);
}
})
return res;
};
字典
字典是什么
- 与集合类似,字典也是存储唯一值的数据结构,但他是以键值对的形式来存储
- ES6 中有字典,Map
- 常用操作:键值对增删查改
const m = new Map();
m.set('key', 'value');
m.get('key');
m.has('key');
m.delete('key');
m.clear(); // 删除所有键值对
leetcode 相关题目
leetcode-349.两个数组的交集(用字典)
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersection = function (nums1, nums2) {
// 求交集,无序且唯一,使用集合
// 时间复杂度 O(n2)
// const set1 = new Set(nums1);
// const set2 = new Set(nums2);
// const set3 = new Set([...set1].filter(item => set2.has(item)));
// return [...set3];
// 用字典
// 时间复杂度 O(2n)
const m = new Map();
const res = [];
nums1.forEach(item => {
m.set(item, true);
})
nums2.forEach(item => {
if (m.get(item)) {
res.push(item);
m.delete(item);
}
})
return res;
};
leetcode-20.有效括号(用栈 + 字典)
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function (s) {
// 长度奇数直接返回false
if (s.length % 2 !== 0) return false;
const stack = [];
const m = new Map();
m.set('(', ')');
m.set('[', ']');
m.set('{', '}');
// 开头右括号直接返回false
if (!m.has(s[0])) return false;
// 循环
for (let i = 0; i < s.length; i++) {
if (m.has(s[i])) {
stack.push(s[i]);
} else {
const top = stack[stack.length - 1];
if (m.get(top) === s[i]) {
stack.pop();
} else {
return false;
}
}
}
return stack.length === 0;
};
leetcode-1.两数之和
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function (nums, target) {
const m = new Map();
for (let i = 0; i < nums.length; i++) {
const n = nums[i];
const t = target - nums[i];
if (m.has(t)) {
return [m.get(t), i];
}
m.set(n, i);
}
return [];
};
leetcode-3.无重复字符的最长子串
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function (s) {
// 左右指针
let l = 0;
let maxLen = 0;
const m = new Map();
for (let r = 0; r < s.length; r++) {
if (m.has(s[r]) && m.get(s[r]) >= l) {
l = m.get(s[r]) + 1;
}
maxLen = Math.max(maxLen, r - l + 1);
m.set(s[r], r);
}
return maxLen;
};
leetcode-76.最小覆盖子串
/**
* @param {string} s
* @param {string} t
* @return {string}
*/
var minWindow = function (s, t) {
let l = 0;
let r = 0;
const need = new Map();
let res = '';
for (let i = 0; i < t.length; i++) {
need.set(t[i], need.has(t[i]) ? need.get(t[i]) + 1 : 1);
}
let needType = need.size;
while (r < s.length) {
if (need.has(s[r])) {
need.set(s[r], need.get(s[r]) - 1);
if (need.get(s[r]) === 0) needType--;
}
while (needType === 0) {
const newRes = s.substr(l, r - l + 1);
if (!res || res.length > newRes.length) res = newRes;
if (need.has(s[l])) {
need.set(s[l], need.get(s[l]) + 1);
if (need.get(s[l]) === 1) needType++;
}
l++;
}
r++;
}
return res;
};
树
树是什么
- 一种分层数据的抽象模型
- js 中相关的树:DOM 树、树形组件
- js 中没有树数据结构,可以用 Array 和 Object 实现
- 树的常用操作:深度、广度优先遍历,先中后序遍历
深度、广度优先遍历
一、深度优先遍历
- 尽可能深地搜索树的分支
- 算法步骤:
- 访问根节点
- 对根节点的 children 依次深度优先遍历
const tree = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: []
},
{
val: 'e',
children: []
}
]
},
{
val: 'c',
children: [
{
val: 'f',
children: []
},
{
val: 'g',
children: []
}
]
}
]
}
const dfs = (root) => {
console.log(root);
root.children.forEach(dfs);
}
dfs(tree);
二、广度优先遍历
- 先访问离根节点最近的节点
- 算法步骤:
- 新建一个队列,把根节点入队
- 把队头出队,并访问
- 把队头的 children 依次入队
- 重复 2、3,直到队列为空
const bfs = (root) => {
const q = [root];
while (q.length) {
const top = q.shift();
console.log(top);
top.children.forEach(item => q.push(item))
}
}
bfs(tree);
二叉树先中后序遍历
一、先序遍历
- 算法步骤:
- 访问根节点
- 对根节点的左子树先序遍历
- 对根节点的右子树先序遍历
const binaryTree = {
val: 1,
left: {
val: 2,
left: { val: 4, left: null, right: null },
right: { val: 5, left: null, right: null },
},
right: {
val: 3,
left: { val: 6, left: null, right: null },
right: { val: 7, left: null, right: null },
}
}
// 递归实现
const preorder = (root) => {
if (!root) return;
console.log(root);
preorder(root.left);
preorder(root.right);
}
preorder(binaryTree);
// 栈实现
const stackPreorder = (root) => {
if (!root) return;
const stack = [root];
while (stack.length) {
const top = stack.pop();
console.log(top);
if (top.right) stack.push(top.right);
if (top.left) stack.push(top.left);
}
}
stackPreorder(binaryTree);
二、中序遍历
- 算法步骤:
- 对根节点的左子树中序遍历
- 访问根节点
- 对根节点的右子树中序遍历
// 递归实现
const inorder = (root) => {
if (!root) return;
inorder(root.left);
console.log(root);
inorder(root.right);
}
inorder(binaryTree);
// 栈实现
const stackInorder = (root) => {
if (!root) return;
const stack = [];
let p = root;
while (stack.length || p) {
while (p) {
stack.push(p);
p = p.left;
}
const top = stack.pop();
console.log(top);
p = top.right;
}
}
stackInorder(binaryTree);
三、后序遍历
- 算法步骤:
- 对根节点的左子树后序遍历
- 对根节点的右子树后序遍历
- 访问根节点
// 递归实现
const postorder = (root) => {
if (!root) return;
postorder(root.left);
postorder(root.right);
console.log(root);
}
postorder(binaryTree);
// 栈实现
const stackPostorder = (root) => {
if (!root) return;
const stack = [root];
const outputStack = [];
while (stack.length) {
const top = stack.pop();
outputStack.push(top);
if (top.left) stack.push(top.left);
if (top.right) stack.push(top.right);
}
while (outputStack.length) {
const output = outputStack.pop();
console.log(output);
}
}
stackPostorder(binaryTree);
前端遍历 json 所有节点值
const json = {
a: {b: { c: 1 } },
d: [1, 2]
}
const dfs = (n, path) => {
console.log(n, path);
Object.keys(n).forEach(k => {
dfs(n[k], path.concat(k));
});
}
dfs(json, []);
leetcode 相关题目
leetcode-104.二叉树最大深度
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function (root) {
// 深度优先遍历
let maxLevel = 0;
const dfs = (n, l) => {
if (!n) return;
if (!n.left && !n.right) {
maxLevel = Math.max(maxLevel, l);
}
dfs(n.left, l + 1);
dfs(n.right, l + 1);
}
dfs(root, 1);
return maxLevel;
};
leetcode-111.二叉树最小深度
/**
* @param {TreeNode} root
* @return {number}
*/
var minDepth = function (root) {
// 广度优先遍历更快
if (!root) return 0;
const q = [[root, 1]];
while (q.length) {
const [top, l] = q.shift();
if (!top.left && !top.right) {
return l;
}
if (top.left) q.push([top.left, l + 1]);
if (top.right) q.push([top.right, l + 1]);
}
};
leetcode-102.二叉树的层序遍历
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function (root) {
// 广度优先遍历
if (!root) return [];
const q = [root];
const res = [];
while (q.length) {
let len = q.length;
res.push([]);
while (len--) {
const top = q.shift();
res[res.length - 1].push(top.val);
if (top.left) q.push(top.left);
if (top.right) q.push(top.right);
}
}
return res;
};
leetcode-94.二叉树的中序遍历
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function (root) {
if (!root) return [];
const res = [];
// 栈实现
const stack = [];
let p = root;
while (stack.length || p) {
while (p) {
stack.push(p);
p = p.left;
}
const top = stack.pop();
res.push(top.val);
p = top.right;
}
return res;
};
leetcode-112.路径总和
/**
* @param {TreeNode} root
* @param {number} targetSum
* @return {boolean}
*/
var hasPathSum = function (root, targetSum) {
// 深度优先遍历
if (!root) return false;
let res = false;
const dfs = (n, sum) => {
if (!n) return;
sum += n.val;
if (!n.left && !n.right && sum === targetSum) {
// 叶子节点
res = true;
}
dfs(n.left, sum);
dfs(n.right, sum);
}
dfs(root, 0);
return res;
};
图
图是什么
- 图是网络结构的抽象模型,是一组由边连接的节点
- 图可以表示任意二元关系,如路线、航班
- js 中没有图数据结构,可以用 Array 和 Object 构建
- 图的表示法:领接矩阵、领接表等
图的深度、广度优先遍历
一、图的深度优先遍历
- 算法步骤:
- 访问根节点
- 对根节点的没有访问过的相邻节点挨个进行深度优先遍历
const graph = {
0: [1, 2],
1: [2],
2: [0, 3],
3: [3]
}
const visited = new Set();
const dfs = (n) => {
console.log(n);
visited.add(n);
graph[n].forEach(item => {
if (!visited.has(item)) {
dfs(item);
}
})
}
dfs(2);
二、图的广度优先遍历
- 新建队列,根节点入队
- 把队头出队并访问
- 把队头没访问过的相邻节点入队
- 重复 2、3 步骤,直到队列为空
const visited = new Set();
const q = [2];
visited.add(2);
while (q.length) {
const top = q.shift();
console.log(top);
graph[top].forEach(item => {
if (!visited.has(item)) {
q.push(item);
visited.add(top);
}
})
}
leetcode 相关题目
leetcode-65.有效数字
有效数字图构建:
/**
* @param {string} s
* @return {boolean}
*/
var isNumber = function (s) {
// 构建图(领接表)
// blank 表示空格
// sign 表示 ‘+-’
// e 表示 e
// digit 表示数字
// . 表示 .
// 最终状态为 3、5、6 时,返回 true
const graph = {
0: { 'digit': 6, 'blank': 0, 'sign': 1, '.': 2 },
1: { 'digit': 6, '.': 2 },
2: { 'digit': 3 },
3: { 'digit': 3, 'e': 4 },
4: { 'digit': 5, 'sign': 7 },
5: { 'digit': 5 },
6: { 'digit': 6, '.': 3, 'e': 4 },
7: { 'digit': 5 }
};
let state = 0;
for (let i = 0; i < s.length; i++) {
let c = s[i];
if (c <= '9' && c >= '0') {
c = 'digit';
} else if (c === '+' || c === '-') {
c = 'sign';
} else if (c === ' ') {
c = 'blank';
} else {
c = c.toLocaleLowerCase();
}
if (!graph[state][c]) return false;
state = graph[state][c];
}
return state === 3 || state === 5 || state === 6
};
leetcode-417.太平洋大西洋水流问题
/**
* @param {number[][]} heights
* @return {number[][]}
*/
var pacificAtlantic = function (heights) {
// 图深度优先遍历
// 分别记录从海岸线开始逆流而上能访问到的节点
// 输出既能两个矩阵中都为 true 的节点
if (!heights || !heights[0]) return false
const m = heights.length;
const n = heights[0].length;
// 构建二维数组
const flow1 = Array.from({ length: m }, () => new Array(n).fill(false));
const flow2 = Array.from({ length: m }, () => new Array(n).fill(false));
// 深度优先遍历
const dfs = (r, c, flow) => {
flow[r][c] = true;
// 对满足条件的上下左右节点递归遍历
[[r - 1, c], [r + 1, c], [r, c - 1], [r, c + 1]].forEach(([nr, nc]) => {
if (
// 保证这个节点在矩阵内
nr < m && nc < n && nr >= 0 && nc >= 0 &&
// 保证逆流而上
heights[nr][nc] >= heights[r][c] &&
// 保证这个节点没有被访问过
!flow[nr][nc]
) {
dfs(nr, nc, flow);
}
})
}
for (let r = 0; r < m; r++) {
// 第一列(太平洋海岸线)
dfs(r, 0, flow1);
// 最后一列(大西洋海岸线)
dfs(r, n - 1, flow2);
}
for (let c = 0; c < n; c++) {
// 第一行(太平洋海岸线)
dfs(0, c, flow1);
// 最后一行(大西洋海岸线)
dfs(m - 1, c, flow2);
}
// 输出
const res = [];
for (let r = 0; r < m; r++) {
for (let c = 0; c < n; c++) {
if (flow1[r][c] && flow2[r][c]) {
res.push([r, c]);
}
}
}
return res;
};
leetcode-133.克隆图
/**
* @param {Node} node
* @return {Node}
*/
var cloneGraph = function (node) {
// 拷贝所有节点
// 拷贝所有边
if (!node) return;
// 深度优先遍历
// const visited = new Map();
// const dfs = (n) => {
// const nCopy = new Node(n.val);
// visited.set(n, nCopy);
// (n.neighbors || []).forEach(ne => {
// if (!visited.has(ne)) {
// dfs(ne);
// }
// nCopy.neighbors.push(visited.get(ne));
// })
// }
// dfs(node);
// return visited.get(node);
// 广度优先遍历
const visited = new Map();
visited.set(node, new Node(node.val));
const q = [node];
while (q.length) {
const top = q.shift();
(top.neighbors || []).forEach(ne => {
if (!visited.has(ne)) {
q.push(ne);
visited.set(ne, new Node(ne.val));
}
visited.get(top).neighbors.push(visited.get(ne));
})
}
return visited.get(node);
};
堆
堆是什么
- 堆是一种特殊的完全二叉树
- 所有节点都大于等于(最大堆)或小于等于(最小堆)它的子节点
- js 中通常用数组表示堆
- 左侧子节点的位置是 2*index+1
- 右侧子节点的位置是 2*index+2
- 父节点的位置是 (index-1)/2
堆的应用场景
- 高效、快速找出最大值或最小值 时间复杂度:O(1)
- 找出第 K 个最大(小)元素
- 找出第 K 个最大元素算法步骤:
- 构建一个最小堆(堆顶为最小元素)
- 当容量超过 K,删除堆顶元素
- 插入结束后,堆顶就是第 K 个最大元素
js 实现最小堆类
class MinHeap {
constructor() {
this.heap = [];
}
// 插入
// 时间复杂度:O(logK)
insert(value) {
// 1. 插入节点到堆尾
// 2. 上移,将该节点与父节点交换,直到该节点大于父节点
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
}
// 删除堆顶
// 时间复杂度:O(logK)
pop() {
// 1. 用数组尾部节点替换堆顶(直接删除第一个节点会导致每个节点前移,破坏了整个堆结构)
// 2. 下移,将新堆顶与子节点交换,直到子节点大于这个新的堆顶
if (this.size === 1) return this.heap.shift();
const top = this.heap[0];
this.heap[0] = this.heap.pop();
this.shiftDown(0);
return top;
}
// 上移
shiftUp(index) {
if (index === 0) return;
const parentIndex = this.getParentIndex(index);
if (this.heap[index] < this.heap[parentIndex]) {
// 若当前节点值小于父节点的值,则需要交换该节点于父节点的位置
this.swap(index, parentIndex);
// 递归调用,直到当前节点值大于等于父节点的值
// 因当前节点于父节点下标已完成交换,所以参数为 parentIndex
this.shiftUp(parentIndex);
}
}
// 下移
shiftDown(index) {
const leftIndex = this.getLeftIndex(index);
const rightIndex = this.getRightIndex(index);
if (this.heap[leftIndex] < this.heap[index]) {
this.swap(index, leftIndex);
this.shiftDown(leftIndex);
}
if (this.heap[rightIndex] < this.heap[index]) {
this.swap(index, rightIndex);
this.shiftDown(rightIndex);
}
}
// 获取父节点的 index
getParentIndex(index) {
// 父节点 index 为当前节点 (index - 1) / 2
// >> 1,表示二进制运算:右移一位
return (index - 1) >> 1;
// 完全等价于:
// return Math.floor((index - 1) / 2);
}
// 获取左侧子节点
getLeftIndex(index) {
return 2 * index + 1;
}
// 获取右侧子节点
getRightIndex(index) {
return 2 * index + 2;
}
// 交换节点位置
swap(i1, i2) {
const tmp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = tmp;
}
// 获取堆顶
peek() {
return this.heap[0];
}
// 获取堆大小
size() {
return this.heap.length;
}
}
const h = new MinHeap();
h.insert(3);
h.insert(2);
h.insert(1);
h.heap // [1, 3, 2]
h.pop();
h.heap // [2, 3]
h.peek(); // 3
h.size(); // 2
leetcode 相关题目
leetcode-215.数组中第K个最大元素
class MinHeap {
...
// 实现最小堆类
...
}
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var findKthLargest = function (nums, k) {
const h = new MinHeap();
nums.forEach(item => {
h.insert(item);
if (h.size() > k) {
h.pop();
}
})
return h.peek();
};
leetcode-347.前K个高频元素
class MinHeap {
...
// 实现最小堆类
...
}
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var topKFrequent = function (nums, k) {
const m = new Map();
nums.forEach(item => {
m.set(item, m.has(item) ? m.get(item) + 1 : 1);
})
// js 原生排序,时间复杂度O(n log n)
// const list = Array.from(m).sort((a, b) => b[1] - a[1]).slice(0, k);
// return list.map(item => item[0]);
// 最小堆实现,时间复杂度O(log n)
const h = new MinHeap();
m.forEach((value, key) => {
h.insert({ value, key });
if (h.size() > k) {
h.pop();
}
})
return h.heap.map(item => item.key)
};
leetcode-23.合并K个升序链表
class MinHeap {
...
// 实现最小堆类
...
}
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function (lists) {
// 1. 构建最小堆,并把每个链表头插入堆中
// 2. 弹出堆顶,接入输出链表,并将弹出堆顶所在链表的新链表头插入堆中
// 3. 堆中元素弹出完,则完成合并
let res = new ListNode();
let p = res;
const h = new MinHeap();
lists.forEach(list => {
if (!list) return;
h.insert(list);
})
while (h.size()) {
const top = h.pop();
p.next = top;
p = p.next;
if (top.next) h.insert(top.next);
}
return res.next;
};