递归是工程师最多见的一种解决问题的方式,可是有时候不容易真正掌握。有人说是看起来很简单,本身写起来会费点劲。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% 的用户
因为在数据量不大的状况下,其实性能差距也不大,因此使用递归也是没有毛病的。