繼第一天的「Day 1 - 1. Two Sum - 解法與複雜度 - LeetCode in Swift」,今天來解 160 這題!還沒看過第一天的朋友,歡迎也去看看~
我們開始吧!
難度: Easy / Medium
網址: https://leetcode.com/problems/intersection-of-two-linked-lists/
給予兩個單項鏈結串列 (Singly Linked List) 的 head 節點 headA
和 headB
,回傳兩個 link lists 開始交集的節點;如果沒有交集則回傳 null 。
listA = [4,1,8,4,5]
listB = [5,6,1,8,4,5]
開始交集的地方就是這個節點:
8
class Solution {
func getIntersectionNode(_ headA: ListNode?, _ headB: ListNode?) -> ListNode? {
var aSet = Set<ListNode>()
var a = headA
while let unwrappedA = a {
aSet.insert(unwrappedA)
a = unwrappedA.next
}
var b = headB
while let unwrappedB = b {
if aSet.contains(unwrappedB) { return unwrappedB }
b = unwrappedB.next
}
return nil
}
}
// 為了能存入 Set 必須幫 ListNode 加上 Hashable 實作
extension ListNode: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
public static func == (lhs: ListNode, rhs: ListNode) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
}
令 listA 的個數為 m, listB 的個數為 n
分兩個主要的步驟,和一個結束條件
在許多情境下,我們可以試著提早結束運算來節省時間和資源。
以這個寫法為例,可以加一行判斷如下:
class Solution {
func getIntersectionNode(_ headA: ListNode?, _ headB: ListNode?) -> ListNode? {
// !!!: 如果其中一個節點為 nil ,就可以提早結束:
if headA == nil || headB == nil { return nil }
var aSet = Set<ListNode>()
var a = headA
while let unwrappedA = a {
aSet.insert(unwrappedA)
a = unwrappedA.next
}
var b = headB
while let unwrappedB = b {
if aSet.contains(unwrappedB) { return unwrappedB }
b = unwrappedB.next
}
return nil
}
}
/* Hashable 實作省略 */
以這兩個字串為例
4,1,8,4,5
5,6,1,8,4,5
可以發現全部置右對齊之後,交集的地方很容易就可以找到了。
但是因為資料結構是 singly linked list ,所以無法從尾端走訪;而我們可以發現當先走訪對方那組的之後,就可以把需要比較的對象像上面那樣置右了。
排排站之後像是這樣:
5,6,1,8,4,5|4,1,8,4,5
4,1,8,4,5|5,6,1,8,4,5
根據這個觀察,可以得出核心 routine 的邏輯會是
結束條件是 a === b
,而這個相等有兩個含意
無論是任意一種結束走訪,我們都可以說 a 和 b 都是我們要的結果,因此可以直接回傳 a 或是 b。
class Solution {
func getIntersectionNode(_ headA: ListNode?, _ headB: ListNode?) -> ListNode? {
if headA == nil || headB == nil { return nil }
var a = headA
var b = headB
// 因為是要比較記憶體位置,所以使用 !==
// 當兩者沒有相同時,則繼續走訪
while a !== b {
// 核心 routine
a = a == nil ? headB : a?.next
b = b == nil ? headA : b?.next
}
// or return b
return a
}
}
令 listA 的個數為 m, listB 的個數為 n
解法 | 時間複雜度 | Runtime | 空間複雜度 | Memory | 官方難度定義 |
---|---|---|---|---|---|
Hash Table | O(m+n) | 226 ms | O(m) | 17.07 MB | Easy |
合併走訪 | O(m+n) | 225 ms | O(1) | 16.55 MB | Medium |
第二個合併走訪的解法為符合題目中應對 Follow Up 的解法,官方解答在這個解法還有特別註記這個解法屬於 Medium 。而第一個解法實際上也比較接近在面試時會看到的解法和水準。
Follow up: Could you write a solution that runs in O(m + n) time and use only O(1) memory?
從執行數據來看兩種解法的執行時間差不多,而第二個解法所使用的記憶體較少,但是似乎少到可以忽略可以說是差不了多少。
當初知道這題的第二個解法時,無法整理出能讓自己認同的一個夠有說服力的解法,但是還是想要嘗試寫出來看看。所以當中有任何文中寫不清楚的地方和回饋,都歡迎在下面留言!
今天的解題文章就到這裡,明天見!