Python小白 Leetcode刷題歷程 No.96-No.100 不同的二叉搜索樹、交錯字符串、驗證二叉搜索樹、恢復二叉搜索樹、相同的樹
寫在前面:
作為一個計算機院的大學生,總覺得僅僅在學校粗略的學習計算機專業課是不夠的,尤其是假期大量的空檔期,作為一個小白,實習也莫得路子,又不想白白耗費時間。于是選擇了Leetcode這個平臺來刷題庫。編程我只學過基礎的C語言,現在在自學Python,所以用Python3.8刷題庫。現在我Python掌握的還不是很熟練,算法什么的也還沒學,就先不考慮算法上的優化了,單純以解題為目的,復雜程度什么的以后有時間再優化。計劃順序五個題寫一篇日志,希望其他初學編程的人起到一些幫助,寫算是對自己學習歷程的一個見證了吧。
有一起刷LeetCode的可以關注我一下,我會一直發LeetCode題庫Python3解法的,也可以一起探討。
覺得有用的話可以點贊關注下哦,謝謝大家!
········································································································································································
題解框架:
1.題目,難度
2.題干,題目描述
3.題解代碼(Python3(不是Python,是Python3))
4.或許有用的知識點(不一定有)
5.解題思路
6.優解代碼及分析(當我發現有比我寫的好很多的代碼和思路我就會寫在這里)
········································································································································································
No.96.不同的二叉搜索樹
難度:中等
題目描述:
題解代碼(Python3.8)
class Solution:
def numTrees(self, n: int) -> int:
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
for i in range(2,n+1):
for j in range(i):
dp[i] += dp[j] * dp[i-j-1]
return dp[-1]
或許有用的知識點:
這道題要用到動態規劃的方法。
這道題所求的數在數學上又叫做卡特蘭數(Catalan number),具體介紹如下:
解題思路:
這道題是求解法總數的問題,我們可以使用動態規劃的方法。套用動態規劃算法的模板,對于這道題,令dp[n]為第n位的解法總數,則動態規劃的三要素為:
狀態:假設n為根節點,當i為根節點時,其左子樹節點個數為[1,2,3,...,i-1],右子樹節點個數為[i+1,i+2,...n],所以當i為根節點時,其左子樹節點個數為i-1個,右子樹節點為n-i,即f(i) = G(i-1)G(n-i)。
狀態轉移方程:dp[i]+=dp[j]dp[i-j-1]
邊界條件:dp[0]=1,dp[1]=1。
No.97.交錯字符串
難度:困難
題目描述:
題解代碼(Python3.8)
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
l1 = len(s1)
l2 = len(s2)
l3 = len(s3)
if l1 + l2 != l3:
return False
dp = [[False]*(l2+1) for _ in range(l1+1)]
dp[0][0] = True #首位置
for j in range(1,l2+1): #第一行
dp[0][j] = dp[0][j-1] and s2[j-1] == s3[j-1]
for i in range(1,l1+1): #第一列
dp[i][0] = dp[i-1][0] and s1[i-1] == s3[i-1]
for i in range(1,l1+1):
for j in range(1,l2+1):
dp[i][j] =(dp[i-1][j] and s1[i-1] == s3[i+j-1]) or (dp[i][j-1] and s2[j-1] == s3[i+j-1])
return dp[-1][-1]
或許有用的知識點:
這道題要處理字符串,驗證合法性,可以使用動態規劃的方法,且有動態規劃問題中‘自底向上’思路和‘自頂向下’思路區別的分析(‘自頂向下’的思路可能要用到‘緩存+遞歸’的方法,此時會用到functools.lru_cache裝飾器,再本題’優解代碼及分析中有詳細介紹‘)。
‘自底向上’的思路是比較容易想到的,是先依次解決小問題,最終解決所求問題;在樹形結構中,就是先解決底部的問題,再一層層解決向上的問題,最后解決頂部的所求問題。
而‘自頂向下’的思路是比較不容易想到的,是先看解決最終問題依次需要解決哪些小問題,再解決所有的小問題;在樹形結構中,就是先看解決頂部的問題需要解決哪些子問題,再看解決這些子問題需要解決哪些子問題,直到解決最底部的問題。引用知乎大佬給出的生動形象的小例子:
某日小明上數學課,他的老師給了很多個不同的直角三角板讓小明用尺子去量三角板的三個邊,并將長度記錄下來。兩個小時過去,小明完成任務,把數據拿給老師。老師給他說,還有一個任務就是觀察三條邊之間的數量關系。又是兩個小時,聰明的小明連蹦帶跳走進了辦公室,說:“老師,我找到了,三條邊之中有兩條,它們的平方和約等于另外一條的平方。”老師拍拍小明的頭,“你今天學會了一個定理,勾股定理。它就是說直角三角形有兩邊平方和等于第三邊的平方和”。
另一個故事,某日老師告訴小明“今天要教你一個定理,勾股定理。”小明說,“什么是勾股定理呢?”“勾股定理是說,直角三角形中有兩條邊的平方和等于第三邊的平方。”然后老師給了一大堆直角三角板給小明,讓他去驗證。兩個小時后,小明告訴老師定理是正確的.
兩個故事剛好是語法分析里面對應的兩個方法:第一個故事說的是自底向上的分析方法,第二個故事說的是自頂而下的分析方法。
解題思路:
這道題是處理字符串,驗證合法性的問題,我們可以使用動態規劃的方法。套用動態規劃算法的模板,對于這道題,令dp[i][j]表示s1的前i個字符和s2的前j個字符是否能構成s3的前i+j個字符,則動態規劃的三要素為:
狀態:
第i行第j列的狀態為:s1的前i-1個字符和s2的前j個字符能否構成s3的前i+j?1位,且s1的第i位(s1[i?1])是否等于s3的第i+j位(s3[i+j?1]),或者s1的前i個字符和s2的前j?1個字符能否構成s3的前i+j?1位,且s2的第j位(s2[j?1])是否等于s3的第i+j位(s3[i+j?1])。
狀態轉移方程:dp[i][j] =(dp[i-1][j] and s1[i-1] == s3[i+j-1]) or (dp[i][j-1] and s2[j-1] == s3[i+j-1])
邊界條件:
dp[0][0] = True
for j in range(1,l2+1): dp[0][j] = dp[0][j-1] and s2[j-1] == s3[j-1]
for i in range(1,l1+1): dp[i][0] = dp[i-1][0] and s1[i-1] == s3[i-1]
優解代碼及分析:
優解代碼(Python3.8)
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
l1 = len(s1)
l2 = len(s2)
l3 = len(s3)
import functools
@functools.lru_cache(None)
def recursion(i,j,k):
if i == l1 and j == l2 and k == l3:
return True
if k < l3:
if i < l1 and s1[i] == s3[k] and recursion(i+1,j,k+1):
return True
if j < l2 and s2[j] == s3[k] and recursion(i,j+1,k+1):
return True
return False
return recursion(0,0,0)
分析:
這其實是緩存+遞歸的方式,原理和動態規劃一樣。要用到functools.lru_cache裝飾器。
No.98.驗證二叉搜索樹
難度:中等
題目描述:
題解代碼(Python3.8)
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
res = []
def recursion(root):
if not root:
return
recursion(root.left)
res.append(root.val)
recursion(root.right)
recursion(root)
return (res == sorted(res)) and (len(set(res)) == len(res))
或許有用的知識點:
這道題使用了遞歸的思想。
解題思路:
因為二叉搜索樹中序遍歷是遞增的,所以我們可以中序遍歷判斷前一數是否小于后一個數。運用遞歸的方法,設置一個遞歸函數,將整個樹從左到右輸出,在判斷是否遞增。
No.99.恢復二叉搜索樹
難度:困難
題目描述:
題解代碼(Python3.8)
class Solution:
def recoverTree(self, root: TreeNode) -> None:
self.firstNode = None
self.secondNode = None
self.preNode = TreeNode(float('-inf'))
def recursion(root):
if not root:
return
recursion(root.left)
if (self.firstNode == None) and (self.preNode.val >= root.val):
self.firstNode = self.preNode
if self.firstNode and (self.preNode.val >= root.val):
self.secondNode =root
self.preNode = root
recursion(root.right)
recursion(root)
self.firstNode.val,self.secondNode.val = self.secondNode.val,self.firstNode.val
或許有用的知識點:
這道題用了遞歸的思想。
這道題firstNode,secondNode,preNode三個變量在多個函數中被引用,可以在變量前加’self.’。
float("inf"), float("-inf")分別表示正負無窮。
解題思路:
這道題難點,是找到那兩個交換了的節點,把它交換過來就行了。這里我們二叉樹搜索樹的中序遍歷(中序遍歷遍歷元素是遞增的),如下圖所示, 中序遍歷順序是 4,2,3,1,我們只要找到節點4和節點1交換順序即可。這里我們有個規律發現這兩個節點:第一個節點,是第一個按照中序遍歷時候前一個節點大于后一個節點,我們選取前一個節點,這里指節點4;第二個節點,是在第一個節點找到之后, 后面出現前一個節點大于后一個節點,我們選擇后一個節點,這里指節點1。
No.100.相同的樹
難度:簡單
題目描述:
題解代碼(Python3.8)
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if (not p) and (not q):
return True
if (p and q) and (p.val == q.val):
return self.isSameTree(p.left,q.left) and self.isSameTree(p.right,q.right)
return False
或許有用的知識點:
這道題要用到遞歸的思想。
解題思路:
這道題運用遞歸的方法,設置一個遞歸函數,當p和q都空時return True;當p和q都非空時,如果p和q的值相等,return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right),判斷p和q的左右子支的值是否相等。