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