Golang從合併鏈表聊遞歸
- 2020 年 7 月 5 日
- 筆記
- golang 鏈表合併 遞歸
從合併鏈表聊遞歸
遞歸是工程師最常見的一種解決問題的方式,但是有時候不容易真正掌握。有人說是看起來很簡單,自己寫起來會費點勁。
最著名的例子就是斐波那契數列(Fibonacci sequence),通過尋找遞推公式來計算出結果。
而最近刷到的一道合併鏈表的演算法題,也可以使用遞歸來實現。下面看看題目描述吧:
將兩個升序鏈表合併為一個新的 升序 鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。
示例:
輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4
來源:力扣(LeetCode)
先拋出本人觀點,遞歸的關鍵是:找到邊界條件和遞歸公式。
分析一下題目,可以發現用第一個鏈表l1的頭部節點來去和l2的節點對比,如果大於l2的當前節點,那麼偏移l1的next和l2繼續對比大小。反之如果l1的頭節點對比L2的當前節點更小,那麼就需要對l2做類似處理。
這種不斷對比和偏移的過程,可以總結出一種遞歸公式。
用偽程式碼寫法就是:
if l1.val < l2.val:
l1.next = mergeTwoList(l1.next, l2)
return l1
else:
l2.next = mergeTwoList(l1, l2.next)
return l2
而邊界條件就是在不斷偏移的時候,走到某個鏈表的最後一個節點為止,偽程式碼就是:
if l1 === null:
return l2
if l2 === null:
return l1
用golang來實現,程式碼也很清晰:
type ListNode struct {
Val int
Next *ListNode
}
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
if l1 == nil {
return l2
}
if l2 == nil {
return l1
}
if l1.Val < l2.Val {
l1.Next = mergeTwoLists(l1.Next, l2)
return l1
} else {
l2.Next = mergeTwoLists(l1, l2.Next)
return l2
}
}
在LeetCode裡面提交,運行回饋如下:
執行結果:
通過
顯示詳情
執行用時:
0 ms
, 在所有 Go 提交中擊敗了
100.00%
的用戶
記憶體消耗:
2.6 MB
, 在所有 Go 提交中擊敗了
63.64%
的用戶
可以看到遞歸是非常消耗記憶體的,它循環調用,猶如爾羅斯套娃,一層一層返回內層的調用結果。
如果要優化的話可以使用迭代方式來實現,程式碼需要做一些調整:
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
head := &ListNode{}
result := head
for l1 != nil && l2 != nil {
if l1.Val < l2.Val {
head.Next = l1
head = head.Next
l1 = l1.Next
} else {
head.Next = l2
head = head.Next
l2 = l2.Next
}
}
if l1 == nil {
head.Next = l2
}
if l2 == nil {
head.Next = l1
}
return result.Next
}
可以看出需要創建一個頭部指針來做偏移,而最終result作為一個合成結果鏈表來存儲結果。
最後提交執行,發現結果數據稍微好看了一丟丟:
執行用時:
4 ms
, 在所有 Go 提交中擊敗了
62.28%
的用戶
記憶體消耗:
2.5 MB
, 在所有 Go 提交中擊敗了
100.00%
的用戶
由於在數據量不大的情況下,其實性能差距也不大,所以使用遞歸也是沒有毛病的。