剑指 Offer 35. 复杂链表的复制(138. 复制带随机指针的链表)

题目:

 

 

 

 

 

 

思路:

【1】回溯 + 哈希表(更简洁的说法应该是递归+哈希表),本质上是采用辅助空间来作为记录节点,用原本链表上的节点在哈希表中作为key指向value值的复制出来的链表的节点,这种相对简单易懂。

【2】迭代 + 节点拆分(这种的话可以参考如何将回溯变为双循环,然后其实又是利用一个循环在原本链表上增加节点来替代哈希表)

分为三步:
第一步:第一次循环在原本链表上复制出对应的复制结点,如A`(这种就是复制节点),A为原本节点
A->A`->B->B`->C->C`
第二步:第二次循环,填充复制结点的随机指向(这里因为原本的随机指向一下个就是复制的随机指向了)
第三步断开与原本之间的连接,抽出复制节点。

 

代码展示:

迭代+节点拆分的形式:

//时间0 ms击败100%
//内存41.5 MB击败5.24%
//时间复杂度:O(n),其中 n 是链表的长度。我们只需要遍历该链表三次。
//空间复杂度:O(1)。注意返回值不计入空间复杂度。
/*// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node nodeNew = new Node(node.val);
            nodeNew.next = node.next;
            node.next = nodeNew;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node nodeNew = node.next;
            nodeNew.random = (node.random != null) ? node.random.next : null;
        }
        Node headNew = head.next;
        for (Node node = head; node != null; node = node.next) {
            Node nodeNew = node.next;
            node.next = node.next.next;
            nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null;
        }
        return headNew;
    }
}

 

回溯+哈希表的处理方式:

//时间0 ms击败100%
//内存41.2 MB击败38.5%
//时间复杂度:O(n),其中 n 是链表的长度。对于每个节点,我们至多访问其「后继节点」和「随机指针指向的节点」各一次,均摊每个点至多被访问两次。
//空间复杂度:O(n),其中 n 是链表的长度。为哈希表的空间开销。
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    Map<Node, Node> cachedNode = new HashMap<Node, Node>();

    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        if (!cachedNode.containsKey(head)) {
            Node headNew = new Node(head.val);
            cachedNode.put(head, headNew);
            headNew.next = copyRandomList(head.next);
            headNew.random = copyRandomList(head.random);
        }
        return cachedNode.get(head);
    }
}

利用双循环替代回溯:

//时间0 ms击败100%
//内存41.3 MB击败21.57%
//采用双循环来替代回溯,因为回溯本质上是利用的递归来进行处理,那么其次也是借助了辅助空间。
//然后采用第一次遍历仅简单的复制出链表的节点,第二次循环再填充进随机的指向即可。
//相对于回溯方式更加直观,空间也会相对节省,但是遍历的数量会有所上升,但时间复杂度不变因为O(N+N)其实还是O(N),空间复杂度也不变,但是调用栈的空间节省了,但是代码相对而言没有回溯的简洁。
/*// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    Map<Node, Node> cachedNode = new HashMap<Node, Node>();

    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        //第一次循环仅构建链表
        Node temp = head;
        Node newList = null;
        while (temp != null){
            Node headNew = new Node(temp.val);
            cachedNode.put(temp, headNew);
            if (newList == null){
                newList = headNew;
            }else {
                newList.next = headNew;
                newList = newList.next;
            }
            temp = temp.next;
        }
        //第二次循环才开始填充随机指向
        temp = head;
        newList = cachedNode.get(temp);
        while (temp != null){
            if (temp.random != null) {
                newList.random = cachedNode.get(temp.random);
            }
            temp = temp.next;
            newList = newList.next;
        }

        return cachedNode.get(head);
    }
}

 

posted @ 2023-01-31 15:07  忧愁的chafry  阅读(21)  评论(0编辑  收藏  举报