[递归]递归问题解题思路
遞歸解題三部曲
何為遞歸?程序反復調用自身即是遞歸。
我自己在剛開始解決遞歸問題的時候,總是會去糾結這一層函數做了什么,它調用自身后的下一層函數又做了什么…然后就會覺得實現一個遞歸解法十分復雜,根本就無從下手。
相信很多初學者和我一樣,這是一個思維誤區,一定要走出來。既然遞歸是一個反復調用自身的過程,這就說明它每一級的功能都是一樣的,因此我們只需要關注一級遞歸的解決過程即可。
如上圖所示,我們需要關心的主要是以下三點:
因此,也就有了我們解遞歸題的三部曲:
一定要理解這3步,這就是以后遞歸秒殺算法題的依據和思路。
遞歸的三大要素
第一要素:明確你這個函數想要干什么
對于遞歸,我覺得很重要的一個事就是,這個函數的功能是什么,他要完成什么樣的一件事,而這個,是完全由你自己來定義的。也就是說,我們先不管函數里面的代碼什么,而是要先明白,你這個函數是要用來干什么。
第二要素:尋找遞歸結束條件
所謂遞歸,就是會在函數內部代碼中,調用這個函數本身,所以,我們必須要找出遞歸的結束條件,不然的話,會一直調用自己,進入無底洞。也就是說,我們需要找出當參數為啥時,遞歸結束,之后直接把結果返回,請注意,這個時候我們必須能根據這個參數的值,能夠直接知道函數的結果是什么。
第三要素:找出函數的等價關系式
第三要素就是,我們要不斷縮小參數的范圍,縮小之后,我們可以通過一些輔助的變量或者操作,使原函數的結果不變。
?
例1:求二叉樹的最大深度
先看一道簡單的Leetcode題目: Leetcode 104. 二叉樹的最大深度
題目很簡單,求二叉樹的最大深度,那么直接套遞歸解題三部曲模版:
具體代碼如下:
# Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution(object):def maxDepth(self, root):""":type root: TreeNode:rtype: int"""if root is None:return 0left_height = self.maxDepth(root.left)right_height = self.maxDepth(root.right)return max(left_height, right_height) + 1?
例2:兩兩交換鏈表中的節點
看了一道遞歸套路解決二叉樹的問題后,有點套路搞定遞歸的感覺了嗎?我們再來看一道Leetcode中等難度的鏈表的問題,掌握套路后這種中等難度的問題真的就是秒:Leetcode 24. 兩兩交換鏈表中的節點
直接上三部曲模版:
附上代碼:
# Definition for singly-linked list. # class ListNode(object): # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution(object):def swapPairs(self, head):""":type head: ListNode:rtype: ListNode"""#終止條件:鏈表只剩一個節點或者沒節點了,沒得交換了。返回的是已經處理好的鏈表if head is None or head.next is None:return head#一共三個節點:head, next, swapPairs(next.next)#下面的任務便是交換這3個節點中的前兩個節點newHead = head.nexthead.next = self.swapPairs(newHead.next) newHead.next = head#根據第二步:返回給上一級的是當前已經完成交換后,即處理好了的鏈表部分return newHead?
例3:反轉單鏈表
206. 反轉鏈表
反轉單鏈表。例如鏈表為:1->2->3->4。反轉后為 4->3->2->1
1. 定義遞歸函數功能
假設函數 reverseList(head) 的功能是反轉但鏈表,其中 head 表示鏈表的頭節點
2.尋找結束條件
當鏈表只有一個節點,或者如果是空表的話,你應該知道結果吧?直接啥也不用干,直接把 head 返回
3. 尋找等價關系
它的等價條件中,一定是范圍不斷在縮小,對于鏈表來說,就是鏈表的節點個數不斷在變小,所以,如果你實在找不出,你就先對 reverseList(head.next) 遞歸走一遍,看看結果是咋樣的。
先縮小范圍,先對 2->3->4遞歸下試試,即代碼如下
class Solution:def reverseList(self, head: ListNode) -> ListNode:if not head or not head.next:return headnewHead = self.reverseList(head.next)們在第一步的時候,就已經定義了 reverseLis t函數的功能可以把一個單鏈表反轉,所以,我們對 2->3->4反轉之后的結果應該是這樣:
我們把 2->3->4 遞歸成 4->3->2。不過,1 這個節點我們并沒有去碰它,所以 1 的 next 節點仍然是連接這 2。
接下來呢?該怎么辦?
其實,接下來就簡單了,我們接下來只需要把節點 2 的 next 指向 1,然后把 1 的 next 指向 null,不就行了?,即通過改變 newList 鏈表之后的結果如下:
也就是說,reverseList(head) 等價于 reverseList(head.next) + 改變一下1,2兩個節點的指向。好了,等價關系找出來了,代碼如下(有詳細的解釋):
class Solution:def reverseList(self, head: ListNode) -> ListNode:if not head or not head.next:return headnewHead = self.reverseList(head.next)#改變 1,2節點的指向。#通過 head.next獲取節點2t1 = head.next#讓 2 的 next 指向 2t1.next = head#1 的 next 指向 null.head.next = None#把調整之后的鏈表返回。return newHead?
例4:平衡二叉樹
那么請你先不看以下部分,嘗試解決一下這道easy難度的Leetcode題(個人覺得此題比上面的medium難度要難):Leetcode 110. 平衡二叉樹
我覺得這個題真的是集合了模版的精髓所在,下面套三部曲模版:
找終止條件。 什么情況下遞歸應該終止?自然是子樹為空的時候,空樹自然是平衡二叉樹了。
應該返回什么信息:
為什么我說這個題是集合了模版精髓?正是因為此題的返回值。要知道我們搞這么多花里胡哨的,都是為了能寫出正確的遞歸函數,因此在解這個題的時候,我們就需要思考,我們到底希望返回什么值?
何為平衡二叉樹?平衡二叉樹即左右兩棵子樹高度差不大于1的二叉樹。而對于一顆樹,它是一個平衡二叉樹需要滿足三個條件:它的左子樹是平衡二叉樹,它的右子樹是平衡二叉樹,它的左右子樹的高度差不大于1。換句話說:如果它的左子樹或右子樹不是平衡二叉樹,或者它的左右子樹高度差大于1,那么它就不是平衡二叉樹。
而在我們眼里,這顆二叉樹就3個節點:root、left、right。那么我們應該返回什么呢?如果返回一個當前樹是否是平衡二叉樹的boolean類型的值,那么我只知道left和right這兩棵樹是否是平衡二叉樹,無法得出left和right的高度差是否不大于1,自然也就無法得出root這棵樹是否是平衡二叉樹了。而如果我返回的是一個平衡二叉樹的高度的int類型的值,那么我就只知道兩棵樹的高度,但無法知道這兩棵樹是不是平衡二叉樹,自然也就沒法判斷root這棵樹是不是平衡二叉樹了。
本級遞歸應該做什么。 知道了第二步的返回值后,這一步就很簡單了。目前樹有三個節點:root,left,right。我們首先判斷left子樹和right子樹是否是平衡二叉樹,如果不是則直接返回false。再判斷兩樹高度差是否不大于1,如果大于1也直接返回false。否則說明以root為節點的子樹是平衡二叉樹,那么就返回true和它的高度。
?
判斷左右子樹的高度差是否超過1,依次遞歸
# Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution(object):def isBalanced(self, root):""":type root: TreeNode:rtype: bool"""if root is None:return Trueif abs(self.getHeight(root.left) - self.getHeight(root.right)) > 1:return Falsereturn self.isBalanced(root.left) and self.isBalanced(root.right)def getHeight(self, node):if node is None:return 0return max(self.getHeight(node.left), self.getHeight(node.right)) + 1?
?
?
一些可以用這個套路解決的題
暫時就寫這么多啦,作為一個高考語文及格分,大學又學了工科的人,表述能力實在差因此啰啰嗦嗦寫了一大堆,希望大家能理解這個很好用的套路。
下面我再列舉幾道我在刷題過程中遇到的也是用這個套路秒的題,真的太多了,大部分鏈表和樹的遞歸題都能這么秒,因為樹和鏈表天生就是適合遞歸的結構。
我會隨時補充,正好大家可以看了上面三個題后可以拿這些題來練練手,看看自己是否能獨立快速準確的寫出遞歸解法了。
Leetcode 101. 對稱二叉樹
Leetcode 111. 二叉樹的最小深度
Leetcode 226. 翻轉二叉樹
Leetcode 617. 合并二叉樹
Leetcode 654. 最大二叉樹
Leetcode 83. 刪除排序鏈表中的重復元素
?
https://mp.weixin.qq.com/s/PgSSYc50ajnbh8zD6Ei07g
https://mp.weixin.qq.com/s/mJ_jZZoak7uhItNgnfmZvQ
https://lyl0724.github.io/2020/01/25/1/
總結
以上是生活随笔為你收集整理的[递归]递归问题解题思路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [机器学习] XGBoost 样本不平衡
- 下一篇: [二叉树] 二叉树中的最大路径和---l