songyy5517 / DataStructures-Algorithms

0 stars 0 forks source link

剑指 Offer 35: 复杂链表的复制 #36

Open songyy5517 opened 1 year ago

songyy5517 commented 1 year ago

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

示例 1: This is an image

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
songyy5517 commented 1 year ago

思路1:分治法&哈希表存储节点对(以空间换时间)

  1. 异常处理
  2. 定义链表指针和哈希表
  3. 复制复杂链表的next部分,并将节点对保存至哈希表中(一般链表的复制:设置伪头节点指向真正的头节点)
  4. 根据哈希表,复制复杂链表的random部分
  5. 返回新链表的头节点

复杂度分析:

songyy5517 commented 1 year ago
/*
// 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) {
        // 1. 异常处理
        if (head == null)
            return null;

        // 2. 定义链表指针和哈希表
        Node cur = head;
        HashMap<Node, Node> node_table = new HashMap();

        // 3. 将链表中的节点复制一份到哈希表中
        while(cur != null){
            node_table.put(cur, new Node(cur.val));  //! 
            cur = cur.next;
        }

        // 3. 同时复制链表的next和random部分
        cur = head;
        while(cur != null){
            node_table.get(cur).next = node_table.get(cur.next); 
            node_table.get(cur).random = node_table.get(cur.random);
            cur = cur.next;
        }

        // 4. 返回新链表的头节点
        return node_table.get(head);
    }
}
songyy5517 commented 1 year ago

思路2:拼接 & 拆分

  1. 异常处理;
  2. 定义链表指针;
  3. 第一次遍历:在每个节点后拷贝相同节点;
  4. 第二次遍历:拷贝新链表的random值;
  5. 第三次遍历:拆分链表,将整个大链表拆分成新旧两个链表;
  6. 返回新链表。

复杂度分析:

songyy5517 commented 1 year ago
import java.util.*;
/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/

public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        // 思路:拷贝节点 & 拆分链表
        // 1. 异常处理
        if (pHead == null)
            return null;

        // 2. 在原链表的每个节点后拷贝该节点(A-B -> A-A'-B-B')
        RandomListNode p = pHead;
        while (p != null) {
            RandomListNode new_Node = new RandomListNode(p.label);
            new_Node.next = p.next;
            p.next = new_Node;
            p = new_Node.next;
        }

        // 3. 完成新链表random部分的拷贝
        p = pHead;
        while (p != null){
            if (p.random != null)
                p.next.random = p.random.next;
            p = p.next.next;
        }

        // 4. 完成新链表next部分的拷贝并拆分两个链表
        RandomListNode raw = pHead;
        RandomListNode new_head = pHead.next;
        p = pHead.next;
        while (raw != null) {  
            // 先拆分出原链表(断掉第一根连接)
            raw.next = raw.next.next;
            raw = raw.next;

            // 拆分新链表(断掉第二根连接)
            if (p.next != null)
                p.next = p.next.next;
            p = p.next;    
        }

        return new_head;
    }
}
songyy5517 commented 1 year ago

2023/2/2

  1. 拆分链表时,更新当前指针不能直接用cur.next,因为cur.next已经改变

2024/1/13

  1. 拷贝新链表next部分时,需要将两根链表分干净。(wrong,cannot used original node of list)