遞歸是工程師最多見的一種解決問題的方式,可是有時候不容易真正掌握。有人說是看起來很簡單,本身寫起來會費點勁。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來實現,代碼也很清晰:code
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% 的用戶
能夠看到遞歸是很是消耗內存的,它循環調用,猶如爾羅斯套娃,一層一層返回內層的調用結果。內存
若是要優化的話可使用迭代方式來實現,代碼須要作一些調整:ci
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做爲一個合成結果鏈表來存儲結果。
最後提交執行,發現結果數據稍微好看了一丟丟:List
執行用時: 4 ms , 在全部 Go 提交中擊敗了 62.28% 的用戶 內存消耗: 2.5 MB , 在全部 Go 提交中擊敗了 100.00% 的用戶
因爲在數據量不大的狀況下,其實性能差距也不大,因此使用遞歸也是沒有毛病的。