LeetCode 力扣 138. 複製帶隨機指針的鏈表

題目描述(中等難度)

給一個鏈表,返回複製後的鏈表。鏈表節點相對於普通的多了一個 random 指針,會隨機指向鏈表內的任意節點或者指向 null

思路分析

這道題其實和 133 題 複製一個圖很類似,這裏的話就是要解決的問題就是,當更新當前節點的 random 指針的時候,如果 random 指向的是很後邊的節點,但此時後邊的節點還沒有生成,那麼我們該如何處理。

133 題 一樣,我們可以利用 HashMap 將節點提前生成並且保存起來,第二次遍歷到他的時候直接從 HashMap 裏邊拿即可。

這裏的話就有兩種思路,一種需要遍歷兩邊鏈表,一種只需要遍歷一遍。

2020.3.3 更新,leetcode 增加了樣例,之前沒有重複的數字所以 key 存的 val ,現在有了重複數字,將 key 修改爲 Node。此外 Node 的無參的構造函數也被去掉了,也需要修改。

解法一

首先利用 HashMap 來一個不用思考的代碼。

遍歷第一遍鏈表,我們不考慮鏈表之間的相互關係,僅僅生成所有節點,然後把它存到 HashMap 中,val 作爲 keyNode 作爲 value

遍歷第二遍鏈表,將之前生成的節點取出來,更新它們的 nextrandom 指針。

public Node copyRandomList(Node head) {
    if (head == null) {
        return null;
    }
    HashMap<Node, Node> map = new HashMap<>();
    Node h = head;
    while (h != null) {
        Node t = new Node(h.val); 
        map.put(h, t);
        h = h.next;
    }
    h = head;
    while (h != null) {
        if (h.next != null) {
            map.get(h).next = map.get(h.next);
        }
        if (h.random != null) {
            map.get(h).random = map.get(h.random);
        }
        h = h.next;
    }
    return map.get(head);
}

解法二

解法一雖然簡單易懂,但還是有可以優化的地方的。我們可以只遍歷一次鏈表。

核心思想就是延遲更新它的 next

1 -> 2 -> 3

用 cur 指向已經生成的節點的末尾
1 -> 2   
     ^
     c

然後將 3 構造完成

最後將 2 的 next 指向 3
1 -> 2 -> 3  
     ^
     c
     
期間已經生成的節點存到 HashMap 中,第二次遇到的時候直接從 HashMap 中拿

看下代碼理解一下含義吧

public Node copyRandomList(Node head) {
    if (head == null) {
        return null;
    }
    HashMap<Node, Node> map = new HashMap<>();
    Node h = head;
    Node cur = new Node(-1); //空結點,dummy 節點,爲了方便頭結點計算
    while (h != null) {
        //判斷當前節點是否已經產生過
        if (!map.containsKey(h)) {
            Node t = new Node(h.val);
            map.put(h, t);
        }
        //得到當前節點去更新它的 random 指針
        Node next = map.get(h);
        if (h.random != null) {
            //判斷當前節點是否已經產生過
            if (!map.containsKey(h.random)) {
                next.random = new Node(h.random.val);
                map.put(h.random, next.random);
            } else {
                next.random = map.get(h.random);
            }

        }
        //將當前生成的節點接到 cur 的後邊
        cur.next = next;
        cur = cur.next;
        h = h.next;
    }
    return map.get(head);
}

解法三

上邊的兩種解法都用到了 HashMap ,所以額外需要 O(n) 的空間複雜度。現在考慮不需要額外空間的方法。

主要參考了這裏。主要解決的問題就是我們生成節點以後,當更新它的 random 的時候,怎麼找到之前生成的節點,前兩種解法用了 HashMap 全部存起來,這裏的話可以利用原來的鏈表的指針域。

主要需要三步。

  1. 生成所有的節點,並且分別插入到原有節點的後邊
  2. 更新插入節點的 random
  3. 將新舊節點分離開來

一圖勝千言,大家看一下下邊的圖吧。

代碼對應如下。

public Node copyRandomList(Node head) {
    if (head == null) {
        return null;
    }
    Node l1 = head;
    Node l2 = null;
    //生成所有的節點,並且分別插入到原有節點的後邊
    while (l1 != null) {
        l2 = new Node(l1.val);
        l2.next = l1.next;
        l1.next = l2;
        l1 = l1.next.next;
    }
    //更新插入節點的 random
    l1 = head;
    while (l1 != null) {
        if (l1.random != null) {
            l1.next.random = l1.random.next;
        }
        l1 = l1.next.next;
    }

    l1 = head;
    Node l2_head = l1.next;
    //將新舊節點分離開來
    while (l1 != null) {
        l2 = l1.next;
        l1.next = l2.next;
        if (l2.next != null) {
            l2.next = l2.next.next;
        }
        l1 = l1.next;
    }
    return l2_head;
}

解法四

不利用額外的空間複雜度還有一種思路,參考 這裏

解法三利用原鏈表的 next 域把新生成的節點保存了起來。類似的,我們還可以利用原鏈表的 random 域把新生成的節點保存起來。

主要還是三個步驟。

  1. 生成所有的節點,將它們保存到原鏈表的 random 域,同時利用新生成的節點的 next 域保存原鏈表的 random
  2. 更新新生成節點的 random 指針。
  3. 恢復原鏈表的 random 指針,同時更新新生成節點的 next 指針。

一圖勝千言。

相應的代碼如下。

public Node copyRandomList(Node head) {
    if (head == null) {
        return null;
    }
    Node l1 = head;
    Node l2 = null;
    //生成所有的節點,講它們保存到原鏈表的 random 域,
    //同時利用新生成的節點的 next 域保存原鏈表的 random。
    while (l1 != null) {
        l2 = new Node(l1.val);
        l2.next = l1.random;
        l1.random = l2;
        l1 = l1.next;
    }
    l1 = head;
    //更新新生成節點的 random 指針。
    while (l1 != null) {
        l2 = l1.random;
        l2.random = l2.next != null ? l2.next.random : null;
        l1 = l1.next;
    }

    l1 = head;
    Node l2_head = l1.random;
    //恢復原鏈表的 random 指針,同時更新新生成節點的 next 指針。
    while (l1 != null) {
        l2 = l1.random;
        l1.random = l2.next;
        l2.next = l1.next != null ? l1.next.random : null;
        l1 = l1.next;
    }
    return l2_head;
}

解法一、解法二是比較直接的想法,直接利用 HashMap 存儲之前的節點。解法三、解法四利用原有鏈表的指針,通過指來指去完成了賦值。鏈表操作的核心思想就是,在改變某一個節點的指針域的時候,一定要把該節點的指針指向的節點用另一個指針保存起來,以免造成丟失。

更多詳細通俗題解詳見 leetcode.wang

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章