【路飞】复制带随机指针链表&&循环队列&&双端队列&&前中后队列

161 阅读3分钟

记录 4 道算法题

复制带随机指针的链表

138. 复制带随机指针的链表 - 力扣(LeetCode) (leetcode-cn.com)


要求:

    * 每个节点都有一个 random 指向随机节点
    * 需要复制新节点,指向新节点

复制的时候会遇到的难点是random的指向。当遍历一遍的时候,旧节点是可以替换成新节点的,因为联系的纽带是next,前后紧紧相连。但是 random 很难确定,首先 random 是可以指向任意地方,意味着替换了新节点后 random所指的节点可能还没有复制。如果是全部复制一遍后再指向random的话,也会面临不知道哪个是random的复制节点。

所以需要解决的问题是如何保存random,正确的指向random。

我们可以把新复制的节点插入到旧节点后面,这样新旧节点的联系就确定了。A -> A' -> B -> B'。我们想找到的 random 节点就是旧节点的next。

但是最少要进行3次链表的遍历。第一次是复制节点插入,第二次是赋值 random节点,第三是把复制的新节点收集起来形成新链表。

    function copyRandomList(head) {
        let node = head
        while(node) {
            // 创建新节点并插入
            const copy = new Node(node.val, node.next, null)
            node.next = copy
            node = node.next.next
        }

        node = head
        while(node) {
            // 指random
            const copy = node.next
            copy.random = node.random?.next ?? null
            node = node.next.next
        }

        node = head
        let p = new Node()
        head = p
        while(node) {
            // 收集复制节点
            p = p.next = node.next
            node = node.next = node.next?.next ?? null
        }

        return head.next
    }

还有第二个方法就是递归,这种一看结构就和斐波那契数列的递归很像。节点里面有节点,一直到最里面节点生成才逐步返回。

    const map = new Map()
    function copyRandomList(head) {
        // head可能是null
        if (!head) return head
        
        if (map.has(head)) {
            return map.get(head)
        }
        
        const copy = new Node(head.val)
        // 要马上保存,因为后面递归会先用到
        map.set(head, copy)
        
        copy.next = copyRandomList(head.next)
        copy.random = copyRandomList(head.random)
        
        // 把节点返回出去给 next或者random
        return copy
    }

设计循环队列

622. 设计循环队列 - 力扣(LeetCode) (leetcode-cn.com)


就是个简单的数组,遵循先进先出的规则,直接上代码吧。也可以用链表写。

    class MyCircularQueue {
        constructor(k) {
            this.queue = []
            this.n = k
            // 其实读数组的length也是可以的,我这里用了一个变量指着队列的结尾
            this.i = 0
        }
        
        enQueue(value) {
            if (this.isFull()) return false
            this.queue.push(value)
            this.i++
            return true
        }
        
        deQueue() {
            if (this.isFull()) return false
            this.queue.shift()
            this.i--
            return true
        }
        
        Front() {
            if (this.isEmpty()) return -1
            return this.queue[0]
        }
        
        Rear() {
            if (this.isEmpty()) return -1
            return this.queue[this.i - 1]
        }
        
        isEmpty() {
            return this.i === 0
        }
        
        isFull() {
            return this.i === this.n
        }
    }

设计双端队列

641. 设计循环双端队列 - 力扣(LeetCode) (leetcode-cn.com)


和循环队列比起来多了可以头部插入节点,用数组写就会很简单,和上面的代码差不多。

    class MyCircularDeque {
        constructor(k) {
            this.queue = []
            this.n = k
            // 其实读数组的length也是可以的,我这里用了一个变量指着队列的结尾
            this.i = 0
        }
        
        insertFront(value) {
            if (this.isFull()) return false
            this.i++
            this.queue.unshift(value)
            return true
        }
        
        insertLast(value) {
            if (this.isFull()) return false
            this.i++
            this.queue.push(value)
            return true
        }
        
        deleteFront() {
            if (this.isEmpty()) return false
            this.i--
            this.queue.shift()
            return true
        }
        
        deleteLast() {
            if (this.isEmpty()) return false
            this.i--
            this.queue.pop()
            return true
        }
        
        getFront() {
            if (this.isEmpty()) return -1
            return this.queue[0]
        }
        
        getRear() {
            if (this.isEmpty()) return -1
            return this.queue[this.i - 1]
        }
        
        isEmpty() {
            return this.i === 0
        }
        
        isFull() {
            return this.i === this.n
        }
    }

设计前中后队列

leetcode-cn.com/problems/de…


多了插入中间节点,分为单双数的情况,中间是不一样的。双数的时候中间是中间两个的前一个,例如 1, 2, 3, 4。中间是 2。随着数组长度的变化,中间节点会变化,但一定是 +1. 另外,如果用链表实现的话, 寻找中间节点的方法可以是用一个变量记录个数。或者用两个指针一个走一步,一个走两步。等走得快的指针走完,另一个就是指中间的节点,之类的。

    class FrontMiddleBackQueue {
        constructor() {
            this.queue = []
        }
        
        pushFront(val) {
            this.queue.unshift(val)
        }
        
        pushMiddle(val) {
            let i = this.queue.length
            if (i % 2 === 0) {
                i = i / 2
            } else {
                i = i >> 1
            }
            // splice方法 是在 i 的位置插入,然后原本 i 上的数往后移
            this.queue.splice(i, 0, val)
        }
        
        pushBack(val) {
            this.queue.push(val)
        }
        
        popFront() {
            if (this.queue.length === 0) return -1
            return this.queue.shift()
        }
        
        popMiddle() {
            if (this.queue.length === 0) return -1
            let i = this.queue.length
            if (i % 2 === 0) {
                i = i / 2 - 1
            } else {
                i = i >> 1
            }
            return this.queue.splice(i, 1)[0]
        }
        
        popBack() {
            if (this.queue.length === 0) return -1
            return this.queue.pop()
        }
    }

结束