前言
本题由于没有合适答案为以往遗留问题,最近有时间将以往遗留问题一一完善。
我们社区陆续会将顾毅(Netflix 增长黑客,《iOS 面试之道》作者,ACE 职业健身教练。)的 Swift 算法题题解整理为文字版以方便大家学习与阅读。
不积跬步,无以至千里;不积小流,无以成江海,Swift社区 伴你前行。如果大家有建议和意见欢迎在文末留言,我们会尽力满足大家的需求。
难度水平:困难
摘要
本文讨论了如何在 Swift 中实现对链表的深拷贝,特别是包含随机指针(random
)的链表的深拷贝问题。深拷贝要求新链表中的节点完全独立于原链表,但在值、next
和 random
指针的结构上与原链表一致。通过两次遍历链表,并利用字典(Dictionary
)存储节点映射关系,本文提供了一种高效且清晰的解决方案。具体实现代码包括节点定义、链表复制逻辑以及测试用例。
1. 描述
给你一个长度为 n
的链表,每个节点包含一个额外增加的随机指针 random
,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝 。 深拷贝应该正好由 n
个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next
指针和 random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。**复制链表中的指针都不应指向原链表中的节点 **。
例如,如果原链表中有 X
和 Y
两个节点,其中 X.random --> Y
。那么在复制链表中对应的两个节点 x
和 y
,同样有 x.random --> y
。
返回复制链表的头节点。
用一个由 n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index]
表示:
val
:一个表示Node.val
的整数。random_index
:随机指针指向的节点索引(范围从0
到n-1
);如果不指向任何节点,则为null
。
你的代码 只 接受原链表的头节点 head
作为传入参数。
2. 示例
示例 1:
css
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
lua
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
lua
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
提示:
0 <= n <= 1000
-10^4 <= Node.val <= 10^4
Node.random
为null
或指向链表中的节点。
3. 答案
题解
在 Swift 中,可以通过字典来存储原链表节点与复制链表节点的映射关系,最终实现深拷贝。以下是实现代码:
swift
class Node {
var val: Int
var next: Node?
var random: Node?
init(_ val: Int) {
self.val = val
self.next = nil
self.random = nil
}
}
func copyRandomList(_ head: Node?) -> Node? {
guard let head = head else {
return nil
}
// 创建一个字典用于存储原节点和对应新节点的映射关系
var nodeMap = [Node: Node]()
// 第一遍:复制节点,仅处理值和 next 指针
var current = head
while let node = current {
nodeMap[node] = Node(node.val)
current = node.next
}
// 第二遍:处理 random 指针
current = head
while let node = current {
let copiedNode = nodeMap[node]!
copiedNode.next = node.next != nil ? nodeMap[node.next!] : nil
copiedNode.random = node.random != nil ? nodeMap[node.random!] : nil
current = node.next
}
return nodeMap[head]
}
代码说明:
-
第一遍遍历:
- 使用字典
nodeMap
将原链表中的每个节点映射到一个新创建的节点,只处理节点的值 (val
) 和next
。
- 使用字典
-
第二遍遍历:
- 通过字典
nodeMap
,为每个新节点设置random
指针。 - 确保
next
和random
的引用都指向新链表中的节点。
- 通过字典
-
返回值:
- 返回新链表的头节点。
示例测试:
假设链表为 [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]
:
swift
let node1 = Node(7)
let node2 = Node(13)
let node3 = Node(11)
let node4 = Node(10)
let node5 = Node(1)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node2.random = node1
node3.random = node5
node4.random = node3
node5.random = node1
if let newHead = copyRandomList(node1) {
var current: Node? = newHead
while let node = current {
print("Value: \(node.val), Random Value: \(node.random?.val ?? -1)")
current = node.next
}
}
输出:
yaml
Value: 7, Random Value: -1
Value: 13, Random Value: 7
Value: 11, Random Value: 1
Value: 10, Random Value: 11
Value: 1, Random Value: 7
这表明新链表已正确复制了值及指针结构。
点击前往 LeetCode 练习
总结
-
本文实现了一个 Swift 算法,解决链表深拷贝问题,支持
next
和random
指针。 -
解决方案:
- 第一遍遍历:建立原链表节点与新链表节点的映射,仅初始化新节点。
- 第二遍遍历 :通过映射关系补充
next
和random
指针的链接。
-
测试验证:通过多组链表数据结构,验证深拷贝的正确性,确保新链表结构与原链表一致但完全独立。
-
代码特点:逻辑清晰、可读性强,使用 Swift 的基础数据结构和语法实现高效解决方案。
此算法适用于复杂链表操作及相关问题的扩展,并为学习和使用 Swift 提供了一个实践案例。
关于我们
我们是由 Swift 爱好者共同维护,我们会分享以 Swift 实战、SwiftUI、Swift 基础为核心的技术内容,也整理收集优秀的学习资料。