Day15 二叉樹Part2 初見回溯(二叉樹相關)

haohaoscnblogs發表於2024-07-31

目錄
  • 任務
    • 110.平衡二叉樹
      • 思路
    • 257. 二叉樹的所有路徑
      • 思路
    • 404. 左葉子之和
      • 思路
    • 513.找樹左下角的值
      • 思路
  • 心得體會

任務

110.平衡二叉樹

給定一個二叉樹,判斷它是否是 平衡二叉樹

思路

典型的樹形DP,每個節點向它的左右孩子收集資訊,然後利用收集到的資訊判斷當前節點,最後再將資訊傳給上層。對於本題,每個節點要判斷以自己為根的樹是否是平衡二叉樹,需要判斷3個條件:

  1. 自己的左子樹是否平衡
  2. 自己的右子樹是否平衡
  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 ,按 任意順序 ,返回所有從根節點到葉子節點的路徑。

思路

採用遍歷的思路:

  1. 對於任意節點,每次首次遍歷到節點就將其加入到path中。
  2. 對於任意節點,遞迴遍歷其左右節點
  3. 遇到葉子節點後,將之前收集到的路徑加入到paths中。
  4. 從任意節點退出後,需要回溯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)

心得體會

更新了對二叉樹章節中遞迴的思考。

  1. 上節課中樹形DP的思路,一般只考慮單層遞迴邏輯和遞迴基,如我這個節點和我左右孩子要做什麼(區域性),我左右子樹要做什麼(遞迴)
  2. 本節的二叉樹的所有路徑,左葉子之和以及找樹左下角的值都是考慮遍歷的思路,也就是在思考上,需要我們考慮,我們是用遞迴序在遍歷這棵樹,判斷我們在遍歷過程中需要收集的資訊,放入全域性變數,然後再考慮單層邏輯,特殊節點邏輯等。特別的,單層邏輯中,需要考慮相對全域性變數的回溯,或者利用區域性變數隱形回溯。(注:這裡的全域性變數指的是比當前區域性作用域更大的變數,如可變型別的引數,類的成員等)單層邏輯中一般考慮該節點怎麼收集,離開該節點時怎麼處理。

也就是說,隨著更多的題目,思路上不再是之前完全不考慮遍歷過程的邏輯。

  • 判斷性質的,計算以某節點為根的某個問題等,一般用樹形DP等傳統遞迴思考方式。
  • 處理特殊的節點,路徑相關的(處理全部節點)等,一般用遍歷(遞迴序)的思考方式。

此外,遞迴過程中的區域性變數肯定是和遞迴函式一致的,即天然會回溯,而為了實現某些功能使用全域性變數時,為了保證全域性變數(如列表)記錄時與遞迴函式中保持一致,才引入了回溯。

相關文章