leetcode110. 平衡二叉树
再强调一波概念:
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
求深度可以从上到下去查 所以需要前序遍历(中左右)
而高度只能从下到上去查,所以只能后序遍历(左右中)
为什么104.二叉树的最大深度中求的是二叉树的最大深度,也用的是后序遍历。
那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这棵树的最大深度,所以才可以使用后序遍历。
而该题是要求比较高度的,所以必然是要后序遍历。
递归三步曲分析:
- 明确递归函数的参数和返回值
参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。
那么如何标记左右子树是否差值大于1呢?
如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。
所以如果已经不是二叉平衡树了,可以返回-1 来标记已经不符合平衡树的规则了。 - 明确终止条件
递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0 - 明确单层递归的逻辑
如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。
分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回 -1,表示已经不是二叉平衡树了。
总结
通过本题可以了解求二叉树深度和二叉树高度的差异:求深度适合用前序遍历,而求高度适合用后序遍历。
class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
return self.getHeight(root) != -1
#
def getHeight(self, node):
if not node:
return 0
# 后序遍历求高度
leftHeight = self.getHeight(node.left)
rightHeight = self.getHeight(node.right)
# 逻辑判断部分(-1的用法)
if leftHeight == -1 or rightHeight == -1 or abs(leftHeight - rightHeight) > 1:
return -1
return 1 + max(leftHeight, rightHeight)
leetcode257. 二叉树的所有路径
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。
前序遍历以及回溯的过程如图:
回溯和递归是一一对应的,有一个递归,就要有一个回溯。
此题建议在本地IDE上debug理解,看看如何递归和回溯的,如下:
from typing import List
# Definition for a binary tree node.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# 构建二叉树(利用递归)
def build_binary_tree(nums):
# 如果给定的列表 nums 是空列表,则返回 None
if not nums:
return None
# 使用递归方式构建二叉树
def helper(index):
if index >= len(nums) or nums[index] is None:
return None
node = TreeNode(nums[index])
node.left = helper(2 * index + 1)
node.right = helper(2 * index + 2)
return node
root = helper(0)
return root
# 测试代码(层序遍历, 每一层都得写上,即便为空. 比如深度为3的树就得填7个元素)
#nums = [1, 2, 3, 4, None, None, 7]
nums = [1,2,3,None,5,None,None]
root = build_binary_tree(nums)
def binaryTreePaths(root: TreeNode) -> List[str]:
result = []
path = []
if not root:
return result
traversal(root, path, result)
return result
def traversal(cur, path, result):
# 中,前序遍历。(中为什么写在这里,因为最后一个节点也要加入到path中)
path.append(cur.val)
# 如果到达叶子节点
if not cur.left and not cur.right:
sPath = '->'.join(map(str, path))
result.append(sPath)
return
# 左节点
if cur.left:
traversal(cur.left, path, result)
path.pop() # 回溯
# 右节点
if cur.right:
traversal(cur.right, path, result)
path.pop() # 回溯
print(binaryTreePaths(root))
leetcode404. 左叶子之和
这题思路,首先要注意是判断左叶子,不是二叉树左侧节点,所以不要上来想着层序遍历。
左叶子的明确定义:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点。
判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。
如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子,判断代码如下:
if root.left and not root.left.left and not root.left.right:
左叶子节点处理逻辑
总结:
平时我们解二叉树的题目时,已经习惯了通过节点的左右孩子判断本节点的属性,而本题我们要通过节点的父节点判断本节点的属性。
class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
# 排除没有左叶子的情况
if not root:
return 0
if not root.left and not root.right:
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)
# 中
sumValue = leftValue + rightValue
return sumValue