Description
給定一個鏈結串列,每個節點除了
next指針外,還有一個random指針可以指向串列中任意節點或 null。回傳該串列的深拷貝。
Example:
Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]] Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]
Intuition#
核心思路:用 HashMap 建立「原節點 -> 新節點」的映射,解決 random 指針無法在第一次遍歷時確定的問題。
- 難點在於 random 指針可能指向尚未建立的節點
- HashMap 方法:先建立所有新節點的映射,再設定 next 和 random
- 進階:Interleave 方法可在 O(1) 空間內完成
Approaches#
1: HashMap (Two-Pass)#
- 概念: 第一次遍歷建立所有新節點並存入 HashMap(原節點 -> 新節點);第二次遍歷透過 HashMap 設定 next 和 random
- 時間複雜度:
O(n)- 兩次遍歷 - 空間複雜度:
O(n)- HashMap 存放 n 個映射
class Solution {
fun copyRandomList(node: Node?): Node? {
if (node == null) return null
val map = HashMap<Node, Node>()
// 第一次遍歷:建立所有新節點
var curr = node
while (curr != null) {
map[curr] = Node(curr.`val`)
curr = curr.next
}
// 第二次遍歷:設定 next 和 random
curr = node
while (curr != null) {
map[curr]!!.next = map[curr.next]
map[curr]!!.random = map[curr.random]
curr = curr.next
}
return map[node]
}
}⭐2: Interleave (O(1) Space)#
- 概念: 三步驟:(1) 在每個原節點後插入其複製節點 (2) 利用交錯結構設定 random (3) 分離原串列和複製串列
- 時間複雜度:
O(n)- 三次遍歷 - 空間複雜度:
O(1)- 不需要額外資料結構(輸出不算)
class Solution {
fun copyRandomList(node: Node?): Node? {
if (node == null) return null
// Step 1: 在每個節點後插入複製節點 A -> A' -> B -> B'
var curr = node
while (curr != null) {
val copy = Node(curr.`val`)
copy.next = curr.next
curr.next = copy
curr = copy.next
}
// Step 2: 設定複製節點的 random
curr = node
while (curr != null) {
curr.next!!.random = curr.random?.next
curr = curr.next!!.next
}
// Step 3: 分離兩個串列
val dummy = Node(0)
var copyTail = dummy
curr = node
while (curr != null) {
val copy = curr.next!!
val next = copy.next
copyTail.next = copy
copyTail = copy
curr.next = next
curr = next
}
return dummy.next
}
}Interleave 方法在分離時必須同時還原原串列,否則會破壞原始資料結構。面試中若不確定,HashMap 方法更安全。
補充:One-Pass HashMap with getOrPut
利用 getOrPut 在一次遍歷中同時建立節點和設定指針。遇到新節點時自動建立,遇到已存在的節點則直接取用。
class Solution {
fun copyRandomList(node: Node?): Node? {
if (node == null) return null
val map = HashMap<Node, Node>()
fun getOrCreate(n: Node?): Node? {
if (n == null) return null
return map.getOrPut(n) { Node(n.`val`) }
}
var curr = node
while (curr != null) {
val copy = getOrCreate(curr)!!
copy.next = getOrCreate(curr.next)
copy.random = getOrCreate(curr.random)
curr = curr.next
}
return map[node]
}
}🔑 Takeaways#
- Pattern: 「原節點 -> 新節點」的映射是深拷貝問題的核心思路
- 關鍵技巧: Interleave 技巧(在原節點後插入複製節點)可以巧妙地利用位置關係取代 HashMap,達到 O(1) 空間