- 任務
- 110.平衡二叉樹
- 思路
- 257. 二叉樹的所有路徑
- 思路
- 404. 左葉子之和
- 思路
- 513.找樹左下角的值
- 思路
- 110.平衡二叉樹
- 心得體會
任務
110.平衡二叉樹
給定一個二叉樹,判斷它是否是 平衡二叉樹
思路
典型的樹形DP,每個節點向它的左右孩子收集資訊,然後利用收集到的資訊判斷當前節點,最後再將資訊傳給上層。對於本題,每個節點要判斷以自己為根的樹是否是平衡二叉樹,需要判斷3個條件:
- 自己的左子樹是否平衡
- 自己的右子樹是否平衡
- 自己的左右高度是否相差<=1
只有滿足這三個條件,以當前節點為根的二叉樹才平衡,返回給上層。
class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
height,balanced = self.isNodeBalanced(root)
return balanced
def isNodeBalanced(self,node):
if not node: return 0,True
leftHeight,isLeftB = self.isNodeBalanced(node.left)
rightHeight,isRighttB = self.isNodeBalanced(node.right)
curNodeHeight = max(leftHeight,rightHeight)+1
res = abs(leftHeight - rightHeight) <= 1 and isLeftB and isRighttB
return curNodeHeight,res
257. 二叉樹的所有路徑
給你一個二叉樹的根節點 root ,按 任意順序 ,返回所有從根節點到葉子節點的路徑。
思路
採用遍歷的思路:
- 對於任意節點,每次首次遍歷到節點就將其加入到path中。
- 對於任意節點,遞迴遍歷其左右節點
- 遇到葉子節點後,將之前收集到的路徑加入到paths中。
- 從任意節點退出後,需要回溯path。
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
if not root: return []
paths =[]
path = []
self.dfs(path,root,paths)
return paths
#解法1:當前節點退出時,回溯處理,元素出棧
def dfs(self,path,node,paths):
if not node: return
path.append(node.val)
if (not node.left and not node.right):
paths.append('->'.join(map(str,path)))
else:
self.dfs(path,node.left,paths)
self.dfs(path,node.right,paths)
path.pop()
程式碼隨想錄與我的思路有些許不同,他的思路是任意節點的子節點返回時回溯,特別的,對於葉子節點,因為左右都為空,所以直接返回。細微之處需要細細體會,程式碼如下:
#解法2: 當前節點的左右孩子返回時,回溯處理,元素出棧
def dfs(self,path,node,paths):
path.append(node.val) #當前節點加入到路徑
if (not node.left and not node.right): # 作為葉子節點的處理
paths.append('->'.join(map(str,path)))
return
else:
if node.left:
self.dfs(path,node.left,paths)
path.pop()
if node.right:
self.dfs(path,node.right,paths)
path.pop() #處理完後當前節點從路徑中移除
此外,還可以用隱形回溯,即使用當前層遞迴函式的區域性變數記錄路徑,從下一層返回時就可以不用顯示的呼叫pop,區域性變數就會從之前的函式棧中彈出而保證正確性了。
def dfs(self,node,path,paths):
if not node: return
new_path = path[:] + [node.val]
if not node.left and not node.right:
paathStr = '->'.join(map(str,new_path))
paths.append(paathStr)
self.dfs(node.left,new_path,paths)
self.dfs(node.right,new_path,paths)
404. 左葉子之和
給定二叉樹的根節點 root ,返回所有左葉子之和。
思路
由於int型別為不可變型別,所以用成員變數統計sum,也是遍歷的思路,遞迴遍歷左右,到達左葉子時處理資訊(修改sum)。
class Solution:
def __init__(self):
self.sum = 0
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
self.dfs(root)
return self.sum
def dfs(self,node):
if not node: return 0
self.dfs(node.left)
self.dfs(node.right)
if node.left:
if not node.left.left and not node.left.right: # 左葉子
self.sum += node.left.val
個人覺得程式碼隨想錄的方法在理解上比較難,不是很直觀,其程式碼如下:
class Solution:
def sumOfLeftLeaves(self, root):
if root is None:
return 0
if root.left is None and root.right is None:
return 0
leftValue = self.sumOfLeftLeaves(root.left) # 左
if root.left and not root.left.left and not root.left.right: # 左子樹是左葉子的情況
leftValue = root.left.val
rightValue = self.sumOfLeftLeaves(root.right) # 右
sum_val = leftValue + rightValue # 中
return sum_val
513.找樹左下角的值
給定一個二叉樹的 根節點 root,請找出該二叉樹的 最底層 最左邊 節點的值。
假設二叉樹中至少有一個節點。
思路
遍歷思路,每次遇到葉子節點,判斷其是否是最底層的。
實際編碼中,使用depth記錄當前節點的深度,使用maxDepth記錄最大深度。
由於depth>self.maxDepth,所以每次最底層碰到的第一個節點就會被記錄,而同層其他的節點的深度等於該節點,所以不會被更新。
class Solution:
def __init__(self):
self.maxDepth = float('-inf')
self.res = 0
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
self.dfs(root,0)
return self.res
def dfs(self,node,depth):
if not node: return
if not node.left and not node.right: # 葉子節點
if depth > self.maxDepth:
self.maxDepth = depth
self.res = node.val
self.dfs(node.left,depth+1)
self.dfs(node.right,depth+1)
心得體會
更新了對二叉樹章節中遞迴的思考。
- 上節課中樹形DP的思路,一般只考慮單層遞迴邏輯和遞迴基,如我這個節點和我左右孩子要做什麼(區域性),我左右子樹要做什麼(遞迴)
- 本節的二叉樹的所有路徑,左葉子之和以及找樹左下角的值都是考慮遍歷的思路,也就是在思考上,需要我們考慮,我們是用遞迴序在遍歷這棵樹,判斷我們在遍歷過程中需要收集的資訊,放入全域性變數,然後再考慮單層邏輯,特殊節點邏輯等。特別的,單層邏輯中,需要考慮相對全域性變數的回溯,或者利用區域性變數隱形回溯。(注:這裡的全域性變數指的是比當前區域性作用域更大的變數,如可變型別的引數,類的成員等)單層邏輯中一般考慮該節點怎麼收集,離開該節點時怎麼處理。
也就是說,隨著更多的題目,思路上不再是之前完全不考慮遍歷過程的邏輯。
- 判斷性質的,計算以某節點為根的某個問題等,一般用樹形DP等傳統遞迴思考方式。
- 處理特殊的節點,路徑相關的(處理全部節點)等,一般用遍歷(遞迴序)的思考方式。
此外,遞迴過程中的區域性變數肯定是和遞迴函式一致的,即天然會回溯,而為了實現某些功能使用全域性變數時,為了保證全域性變數(如列表)記錄時與遞迴函式中保持一致,才引入了回溯。