• 第一章、数据结构与算法


    目录

    一、数据结构与算法简介

    1、理论
    * 数据结构:栈、队列、集合、链表、字典、树、图、堆
    * 进阶算法:冒泡算法、选择算法、插入算法、归并算法、快速算法、顺序算法、二分搜索
    * 算法设计思想:分而治之、动态规则、贪心、回溯
    * 重点关注:数据结构与算法的特点、应用场景、js实现、时间/空间复杂度
    
    2、刷题
    * 刷题网站:leetcode
    * 重点关注:通用套路、时间/空间复杂度分析和优化
    
    3、数据结构与算法
    * 是什么:
        - 数据结构:计算机存储、组织数据的方式
        - 算法:一系列解决问题的清晰指令
    * 程序:数据结构 + 算法
    * 关系:数据结构为算法提供服务,算法围绕数据结构操作
    

    二、时间空间复杂度计算

    1、时间复杂度计算
    • 是什么
    * 一个函数,用大O表示,比如O(1)、O(n)、O(logN)...
    * 定性描述该算法的运行时间
    
    • O(1)
    let i = 0
    i += 1
    
    • O(n)
    // O(1) + O(n) = O(n)
    for (let i = 0; i < n; i++) {
        console.log(i)
    }
    
    • O(n^2)
    // O(n) * O(n) = O(n^2)
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            console.log(i, j)
        }
    }
    
    • O(logN)
    // 如果a的x次方等于N,其中a>0且a不等于1,那么数x叫做以a为底N的对数
    // 2的x次方等于N,x等于logN(计算机中2会省略不写,数学中2不可以省略)
    let i = 1
    while (i < n) {
        console.log(i)
        i *= 2
    }
    
    2、空间复杂度计算
    • 是什么
    * 一个函数,用大O表示,比如O(1)、O(n)、O(n^2)...
    * 算法在运行过程中临时占用存储空间大小的量度
    
    • O(1)
    let i = 0
    i += 1
    
    • O(n)
    const list = []
    for (let i = 0; i < n; i++) {
        list.push(i)
    }
    
    • O(n^2)
    const matrix = []
    for (let i = 0; i < n; i++) {
        matrix.push([])
        for (let j = 0; j < n; j++) {
            matrix[i].push(j)
        }
    }
    

    三、数据结构-栈

    1、栈简介
    // 1、栈是一种后进先出的数据结构
    // 2、栈常用操作:push、pop、stack[stack.length-1]
    const stack = []
    stack.push(1)
    stack.push(2)
    const item1 = stack.pop()
    const item2 = stack.pop()
    
    2、栈的应用场景
    * 十进制转二进制
    * 判断字符串的括号是否有效
    * 函数调用堆栈
    
    3、力扣解题(20. 有效的括号)
    var isValid = function (s) {
        const lp = "([{"
        if (s.length % 2 === 1 || lp.indexOf(s[0]) === -1) {
            return false
        }
        const stack = []
        const rp = ")]}"
        for (let i = 0; i < s.length; i++) {
            if (lp.indexOf(s[i]) !== -1) {
                stack.push(s[i])
            } else {
                const top = stack.pop()
                if (lp.indexOf(top) !== rp.indexOf(s[i])) {
                    return false
                }
            }
        }
        return !stack.length
    };
    
    4、力扣解题(144. 二叉树的前序遍历)
    // 二叉树前序:根-左-右
    // 二叉树中序:左-根-右
    // 二叉树后序:左-右-根
    var preorderTraversal = function (root) {
        let arr = []
        let preorder = function (node, arr) {
            if (node === null) return arr
            arr.push(node.val)
            preorder(node.left, arr)
            preorder(node.right, arr)
        }
        preorder(root, arr)
        return arr
    };
    

    四、数据结构-队列

    1、队列简介
    // 1、队列是一种先进先出的数据结构
    // 2、队列常用操作:push、shift、queue[0]
    const queue = []
    queue.push(1)
    queue.push(2)
    const item1 = queue.shift()
    const item2 = queue.shift()
    
    2、队列的应用场景
    * 食堂排队打饭
    * js异步中的任务队列
    * 计算最近请求次数
    
    3、力扣解题(933. 最近的请求次数)
    var RecentCounter = function () {
        this.q = []
    };
    RecentCounter.prototype.ping = function (t) {
        this.q.push(t)
        while (this.q[0] < t - 3000) {
            this.q.shift()
        }
        return this.q.length
    };
    

    五、数据结构-链表

    1、链表简介
    /**
     * 一、链表是什么?
     *     - 多个元素组成的列表
     *     - 元素存储不连续,用next指针连在一起
     * 二、数组vs链表
     *     - 数组:增删非首尾元素时往往需要移动元素
     *     - 链表:增删非首尾元素,不需要移动元素,只需要更改next的指向即可
     * 三、链表常用操作:修改next、遍历链表
     */
    const a = {val: "a"}
    const b = {val: "b"}
    const c = {val: "c"}
    a.next = b
    b.next = c
    // 遍历
    let p = a
    while (p) {
        // console.log(p)
        p = p.next
    }
    // 添加
    const d = {val: "d"}
    b.next = d
    d.next = c
    console.log(a)
    // 删除
    // b.next = c
    // console.log(a)
    
    2、力扣解题(237. 删除链表中的节点)
    var deleteNode = function (node) {
        node.val = node.next.val
        node.next = node.next.next
    };
    
    3、力扣解题(206. 反转链表)
    var reverseList = function(head) {
        let p = null
        while (head) {
            head.prev = head.next
            head.next = p
            p = head
            head = head.prev
        }
        return p
    };
    
    4、力扣解题(2. 两数相加)
    var addTwoNumbers = function (l1, l2) {
        const l3 = new ListNode(0)
        let p = l3
        let carry = 0
        while (l1 || l2) {
            const v1 = l1 ? l1.val : 0
            const v2 = l2 ? l2.val : 0
            p.next = new ListNode((v1 + v2 + carry) % 10)
            carry = Math.floor((v1 + v2 + carry) / 10)
            if (l1) {
                l1 = l1.next
            }
            if (l2) {
                l2 = l2.next
            }
            p = p.next
        }
        if (carry) {
            p.next = new ListNode(carry)
        }
        return l3.next
    };
    
    5、力扣解题(83. 删除排序链表中的重复元素)
    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
    };
    
    6、力扣解题(141. 环形链表)
    var hasCycle = function (head) {
        while (head) {
            if (head.visited) {
                return true
            }
            head.visited = true
            head = head.next
        }
        return false
    };
    

    六、数据结构-集合

    1、集合简介
    /**
     * 一、什么是集合?
     *     - 一种无序且唯一的数据结构
     *     - es6中有集合,名为Set
     *     - 集合的常用操作:去重、判断某元素是否在集合中、求交集
     */
    // 去重
    const arr = [1, 1, 2, 2]
    const arr2 = [...new Set(arr)]
    
    // 判断元素是否在集合中
    const set = new Set(arr)
    const has = set.has(3)
    
    // 求交集
    const set2 = new Set([2, 3])
    const set3 = new Set([...set].filter(item => set2.has(item)))
    
    2、力扣解题(349. 两个数组的交集)
    var intersection = function (nums1, nums2) {
        return [...new Set(nums1)].filter(item => nums2.includes(item))
    };
    

    七、数据结构-字典

    1、字典简介
    /**
     * 一、什么是字典?
     *     - 与集合类似,字典也是一种存储唯一值的数据结构,但它是以键值对的形式来存储
     *     - es6中有字典,名为Map
     *     - 字典的常用操作:键值对的增删改查
     */
    const m = new Map();
    
    // 增
    m.set("a", "aaa")
    m.set("b", "bbb")
    
    // 删
    m.delete("b")
    // m.clear()
    
    // 改
    m.set("a", "aaaaaa")
    
    2、力扣解题(1. 两数之和)
    var twoSum = function (nums, target) {
        const map = new Map()
        for (let i = 0; i < nums.length; i++) {
            const n = nums[i]
            const n2 = target - n
            if (map.has(n2)) {
                return [map.get(n2), i]
            } else {
                map.set(n, i)
            }
        }
    };
    
    3、力扣解题(3. 无重复字符的最长子串)
    var lengthOfLongestSubstring = function (s) {
        let l = 0
        let res = 0
        const map = new Map();
        for (let i = 0; i < s.length; i++) {
            if (map.has(s[i]) && map.get(s[i]) >= l) {
                l = map.get(s[i]) + 1
            }
            res = Math.max(res, i - l + 1)
            map.set(s[i], i)
        }
        return res
    };
    
    4、力扣解题(76. 最小覆盖子串)
    var minWindow = function (s, t) {
        let l = 0
        let r = 0
        const need = new Map()
        for (const c of t) {
            need.set(c, need.has(c) ? need.get(c) + 1 : 1)
        }
        let needType = need.size
        let res = ""
        while (r < s.length) {
            const c = s[r]
            if (need.has(c)) {
                need.set(c, need.get(c) - 1)
                if (need.get(c) === 0) needType -= 1
            }
            while (needType === 0) {
                const newRes = s.substring(l, r + 1)
                if (!res || newRes.length < res.length) res = newRes
                const c2 = s[l]
                if (need.has(c2)) {
                    need.set(c2, need.get(c2) + 1)
                    if (need.get(c2) === 1) needType += 1
                }
                l += 1
            }
            r += 1
        }
        return res
    };
    

    八、数据结构-树

    1、树简介
    /**
     * 一、什么是树?
     *     - 一种分层数据的抽象模型
     *     - 前端工作中常见的树包括:dom树、级联选择、树形控件
     *     - 树的常用操作:深度/广度优先遍历、先中后序遍历
     * 二、什么是深度/广度优先遍历
     *     - 深度优先遍历:尽可能深的搜索树的分支
     *     - 广度优先遍历:先访问离根节点最近的节点
     */
    const tree = {
        val: "a",
        children: [{
            val: "b",
            children: [{
                val: "d",
                children: []
            }, {
                val: "e",
                children: []
            }]
        }, {
            val: "c",
            children: [{
                val: "f",
                children: []
            }, {
                val: "g",
                children: []
            }]
        }]
    }
    
    /**
     * 三、深度优先遍历算法口诀
     *     - 访问根节点
     *     - 对根节点的children挨个进行深度优先遍历
     */
    const dfs = (root) => {
        console.log(root.val)
        root.children.forEach(dfs)
    }
    dfs(tree)
    
    /**
     * 四、广度优先遍历算法口诀
     *     - 新建一个队列,把根节点入队
     *     - 把对头出队并访问
     *     - 把对头的children挨个入队
     *     - 重复第二、三步,直到队列为空
     */
    const bfs = (root) => {
        const q = [root]
        while (q.length > 0) {
            const n = q.shift()
            console.log(n.val)
            n.children.forEach(child => {
                q.push(child)
            })
        }
    }
    bfs(tree)
    
    2、二叉树的先中后序遍历
    /**
     * 一、二叉树是什么?
     *     - 树中每个节点最多只能有两个子节点
     */
    const bt = {
        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) {
            console.log(root.val)
            preorder(root.left)
            preorder(root.right)
        }
    }
    preorder(bt)
    
    /**
     * 三、中序遍历算法口诀
     *     - 对根节点的左子树进行中序遍历
     *     - 访问根节点
     *     - 对根节点的右子树进行中序遍历
     */
    const inorder = (root) => {
        if (root) {
            inorder(root.left)
            console.log(root.val)
            inorder(root.right)
        }
    }
    inorder(bt)
    
    /**
     * 四、后序遍历算法口诀
     *     - 对根节点的左子树进行后序遍历
     *     - 对根节点的右子树进行后序遍历
     *     - 访问根节点
     */
    const postorder = (root) => {
        if (root) {
            postorder(root.left)
            postorder(root.right)
            console.log(root.val)
        }
    }
    postorder(bt)
    
    3、二叉树的先中后序遍历(非递归版)
    const preorder = (root) => {
        if (root) {
            const stack = [root]
            while (stack.length) {
                const n = stack.pop()
                console.log(n.val)
                if (n.right) stack.push(n.right)
                if (n.left) stack.push(n.left)
            }
        }
    }
    preorder(bt)
    
    const inorder = (root) => {
        if (root) {
            const stack = []
            let p = root
            while (stack.length || p) {
                while (p) {
                    stack.push(p)
                    p = p.left
                }
                const n = stack.pop()
                console.log(n.val)
                p = n.right
            }
        }
    }
    inorder(bt)
    
    const postorder = (root) => {
        if (root) {
            const outputStack = []
            const stack = [root]
            while (stack.length) {
                const n = stack.pop()
                outputStack.push(n)
                if (n.left) stack.push(n.left)
                if (n.right) stack.push(n.right)
            }
            while (outputStack.length) {
                const n = outputStack.pop()
                console.log(n.val)
            }
        }
    }
    postorder(bt)
    
    4、力扣解题(104. 二叉树的最大深度)
    var maxDepth = function (root) {
        let res = 0
        const dfs = (n, l) => {
            if (!n) {
                return;
            }
            if (!n.left && !n.right) {
                res = Math.max(res, l)
            }
            dfs(n.left, l + 1)
            dfs(n.right, l + 1)
        }
        dfs(root, 1)
        return res
    };
    
    5、力扣解题(111. 二叉树的最小深度)
    var minDepth = function (root) {
        if (!root) {
            return 0
        }
        const q = [[root, 1]]
        while (q.length) {
            const [n, l] = q.shift()
            if (!n.left && !n.right) {
                return l
            }
            if (n.left) q.push([n.left, l + 1])
            if (n.right) q.push([n.right, l + 1])
        }
    };
    
    6、力扣解题(102. 二叉树的层序遍历)
    var levelOrder = function (root) {
        if (!root) return []
        const q = [root]
        const res = []
        while (q.length) {
            let len = q.length
            res.push([])
            while (len--) {
                const n = q.shift()
                res[res.length - 1].push(n.val)
                if (n.left) q.push(n.left)
                if (n.right) q.push(n.right)
            }
        }
        return res
    };
    
    7、力扣解题(94. 二叉树的中序遍历)
    var inorderTraversal = function (root) {
        const res = []
        const stack = []
        let p = root
        while (stack.length || p) {
            while (p) {
                stack.push(p)
                p = p.left
            }
            const n = stack.pop()
            res.push(n.val)
            p = n.right
        }
        return res
    };
    
    8、力扣解题(112. 路径总和)
    var hasPathSum = function (root, targetSum) {
        if (!root) return false
        let res = false
        const dfs = (n, s) => {
            if (!n.left && !n.right && s === targetSum) {
                res = true
            }
            if (n.left) dfs(n.left, s + n.left.val)
            if (n.right) dfs(n.right, s + n.right.val)
        }
        dfs(root, root.val)
        return res
    };
    

    九、数据结构-图

    1、图简介
    /**
     * 一、图是什么?
     *     - 图是网络结构的抽象模型,是一组由边连接的节点
     *     - 图可以表示任何二元关系,比如道路、航班
     *     - 图的表示法:领接矩阵,领接表、关联矩阵
     * 二、图的常用操作
     *     - 深度优先遍历:尽可能深的搜索图的分支
     *     - 广度优先遍历:先访问离根节点最近的节点
     */
    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(c => {
            if (!visited.has(c)) {
                dfs(c)
            }
        })
    }
    dfs(2)*/
    
    /**
     * 四、广度优先遍历算法口诀
     *     - 新建一个队列,把根节点入队
     *     - 把队头出队并访问
     *     - 把队头的没访问过的相邻节点入队
     *     - 重复第二、三步,直到队列为空
     */
    const visited = new Set()
    visited.add(2)
    const q = [2]
    while (q.length) {
        const n = q.shift()
        console.log(n)
        graph[n].forEach(c => {
            if (!visited.has(c)) {
                q.push(c)
                visited.add(c)
            }
        })
    }
    
    2、力扣解题(65. 有效数字)
    var isNumber = function (s) {
        const graph = {
            0: {"blank": 0, "sign": 1, ".": 2, "digit": 6,},
            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 c of s.trim().toLowerCase()) {
            if (c >= "0" && c <= "9") {
                c = "digit"
            } else if (c === " ") {
                c = "blank"
            } else if (c === "+" || c === "-") {
                c = "sign"
            }
            state = graph[state][c]
            if (state === undefined) {
                return false
            }
        }
        if (state === 3 || state === 5 || state === 6) {
            return true
        }
        return false
    };
    
    3、力扣解题(417. 太平洋大西洋水流问题)
    var pacificAtlantic = function (heights) {
        if (!heights || !heights[0]) {
            return []
        }
        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 >= 0 && nr < m &&
                    nc >= 0 && nc < n &&
                    // 防止死循环
                    !flow[nr][nc] &&
                    // 保证逆流而上
                    heights[nr][nc] >= heights[r][c]
                ) {
                    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
    };
    
    4、力扣解题(133. 克隆图)
    var cloneGraph = function (node) {
        if (!node) return
        const visited = new Map()
        visited.set(node, new Node(node.val))
        const q = [node]
        while (q.length) {
            const n = q.shift();
            (n.neighbors || []).forEach(ne => {
                if (!visited.has(ne)) {
                    q.push(ne)
                    visited.set(ne, new Node(ne.val))
                }
                visited.get(n).neighbors.push(visited.get(ne))
            })
        }
        return visited.get(node);
    };
    

    十、数据结构-堆

    1、堆简介
    /**
     * 一、堆是什么?
     *     - 堆是一种特殊的完全二叉树
     *     - 所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点
     * 二、js中的堆
     *     - js中通常用数组表示堆
     *     - 左侧子节点的位置是2*index+1
     *     - 右侧子节点的位置是2*index+2
     *     - 父节点位置是(index-1)/2
     * 三、堆的应用
     *     - 堆能高效、快速地找出最大值和最小值,时间复杂度:O(1)
     *     - 找出第k个最大(小)元素
     * 四、第k个最大元素
     *     - 构建一个最小堆,并将元素依次插入堆中
     *     - 当堆的容量超过k,就删除堆顶
     *     - 插入结束后,堆顶就是第k个最大元素
     */
    
    2、javascript实现:最小堆类
    /**
     * 一、实现步骤
     *     - 在类里,声明一个数组,用来装元素
     *     - 主要方法:插入、删除堆顶、获取堆顶、获取堆大小
     */
    class MinHeap {
        constructor() {
            this.heap = []
        }
    
        /**
         * 二、插入
         *     - 将值插入堆的底部,即数组的尾部
         *     - 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值
         *     - 大小为k的堆中插入元素的时间复杂度为O(logk)
         */
        insert(value) {
            this.heap.push(value)
            this.shiftUp(this.heap.length - 1)
        }
    
        shiftUp(index) {
            if (index === 0) {
                return
            }
            const parentIndex = this.getParentIndex(index)
            if (this.heap[parentIndex] > this.heap[index]) {
                this.swap(parentIndex, index)
                this.shiftUp(parentIndex)
            }
        }
    
        getParentIndex(i) {
            // 等价:Math.floor((i - 1) / 2)
            return (i - 1) >> 1
        }
    
        swap(i1, i2) {
            const temp = this.heap[i1]
            this.heap[i1] = this.heap[i2]
            this.heap[i2] = temp
        }
    
        /**
         * 三、删除堆顶
         *     - 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)
         *     - 然后下移:将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶
         *     - 大小为k的堆中删除堆顶的时间复杂度为O(logk)
         */
        pop() {
            this.heap[0] = this.heap.pop()
            this.shiftDown(0)
        }
    
        shiftDown(index) {
            const leftIndex = this.getLeftIndex(index)
            const rightIndex = this.getRightIndex(index)
            if (this.heap[leftIndex] < this.heap[index]) {
                this.swap(leftIndex, index)
                this.shiftDown(leftIndex)
            }
            if (this.heap[rightIndex] < this.heap[index]) {
                this.swap(rightIndex, index)
                this.shiftDown(rightIndex)
            }
        }
    
        getLeftIndex(i) {
            return i * 2 + 1
        }
    
        getRightIndex(i) {
            return i * 2 + 2
        }
    
        /**
         * 四、获取堆顶和堆的大小
         *     - 获取堆顶:返回数组的头部
         *     - 获取堆的大小:返回数组的长度
         */
        peek() {
            return this.heap[0]
        }
    
        size() {
            return this.heap.length
        }
    }
    
    const h = new MinHeap()
    h.insert(3)
    h.insert(2)
    h.insert(1)
    h.pop()
    
    3、力扣解题(215. 数组中的第K个最大元素)
    var findKthLargest = function (nums, k) {
        const h = new MinHeap()
        nums.forEach(n => {
            h.insert(n)
            if (h.size() > k) {
                h.pop()
            }
        })
        return h.peek()
    };
    
    4、力扣解题(347. 前K个高频元素)
    // 最小堆类需做改动的方法
    class MinHeap {
        shiftUp(index) {
            if (index === 0) {
                return
            }
            const parentIndex = this.getParentIndex(index)
            if (this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val) {
                this.swap(parentIndex, index)
                this.shiftUp(parentIndex)
            }
        }
    
        shiftDown(index) {
            const leftIndex = this.getLeftIndex(index)
            const rightIndex = this.getRightIndex(index)
            if (this.heap[leftIndex] && this.heap[leftIndex].val < this.heap[index].val) {
                this.swap(leftIndex, index)
                this.shiftDown(leftIndex)
            }
            if (this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[index].val) {
                this.swap(rightIndex, index)
                this.shiftDown(rightIndex)
            }
        }
    }
    
    var topKFrequent = function (nums, k) {
        const map = new Map()
        nums.forEach(n => {
            map.set(n, map.has(n) ? map.get(n) + 1 : 1)
        })
        const h = new MinHeap()
        map.forEach((value, key) => {
            h.insert({value, key})
            if (h.size() > k) {
                h.pop()
            }
        })
        return h.heap.map(a => a.key)
    };
    
    5、力扣解题(23. 合并K个升序链表)
    // 最小堆类需做改动的方法
    class MinHeap {
        shiftUp(index) {
            if (index === 0) {
                return
            }
            const parentIndex = this.getParentIndex(index)
            if (this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val) {
                this.swap(parentIndex, index)
                this.shiftUp(parentIndex)
            }
        }
    
        pop() {
            if (this.size() === 1) return this.heap.shift()
            const top = this.heap[0]
            this.heap[0] = this.heap.pop()
            this.shiftDown(0)
            return top
        }
    
        shiftDown(index) {
            const leftIndex = this.getLeftIndex(index)
            const rightIndex = this.getRightIndex(index)
            if (this.heap[leftIndex] && this.heap[leftIndex].val < this.heap[index].val) {
                this.swap(leftIndex, index)
                this.shiftDown(leftIndex)
            }
            if (this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[index].val) {
                this.swap(rightIndex, index)
                this.shiftDown(rightIndex)
            }
        }
    }
    
    var mergeKLists = function (lists) {
        const res = new ListNode(0)
        let p = res
        const h = new MinHeap()
        lists.forEach(l => {
            if (l) h.insert(l)
        })
        while (h.size()) {
            const n = h.pop()
            p.next = n
            p = p.next
            if (n.next) h.insert(n.next)
        }
        return res.next
    };
    
  • 相关阅读:
    网曝!互联网公司那些老司机才懂的秘密~~
    中国IT行业薪资:与销售相比,程序员真得很“穷”
    太简单了,教你去掉Java代码中烦人的“!=null”
    怎么判断自己在不在一家好公司?
    内部泄露版!互联网大厂的薪资和职级一览
    重磅!GitHub突然宣布,对全球人免费开放全部核心功能
    痛心!Pandownload开发者被抓!我终于决定使用Docker搭建一个多端同步网盘!
    退税:我承认我有赌的成分
    golang实现的简单优先队列
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (13)解答
  • 原文地址:https://www.cnblogs.com/linding/p/16403777.html
Copyright © 2020-2023  润新知