二叉树解题的思维模式分两类:
1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个traverse
函数配合外部变量来实现,这叫「遍历」的思维模式。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。
3、前中后序是遍历二叉树过程中处理每一个节点的三个特殊时间点
前序位置的代码在刚刚进入一个二叉树节点的时候执行;
后序位置的代码在将要离开一个二叉树节点的时候执行;
中序位置的代码在一个二叉树节点左子树都遍历完,即将开始遍历右子树的时候执行。
二叉树的所有问题,就是让你在前中后序位置注入巧妙的代码逻辑,去达到自己的目的,你只需要单独思考每一个节点应该做什么,其他的不用你管,抛给二叉树遍历框架,递归会在所有节点上做相同的操作。
二叉树遍历框架:
一旦你发现题目和子树有关,那大概率要给函数设置合理的定义和返回值,在后序位置写代码了。
例题:
我们可以根据返回每一个节点的左右子树的最长路径,求得节点的最长路径
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if root is None: #如果根为空,返回0
return 0
leftmax = self.maxDepth(root.left) #利用定义,计算左右子树的最大深度
rightmax = self.maxDepth(root.right)
ret = max(leftmax,rightmax) + 1 #整棵树的最大深度等于左右子树的最大深度取最大值+1
return ret
二叉查找树,又被称为二叉搜索树。其特点如下:设x为二叉查找树中的一个结点,x节点包含关键字key,一句话就是左孩子比父节点小,右孩子比父节点大,还有一个特性就是”中序遍历“可以让结点有序。
- 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
在题目中:
- 二叉搜索树,性质:左树节点值 < 根节点值 < 右树节点值
- 如果root.val < low,那么左树节点值更小,要被剪枝,递归返回trimBST,从root.right开始
- 如果root.val > high,那么右树节点值更大,要被剪枝,递归返回trimBST,从root.left开始
- 若果root.val符合范围,那么就对root.left函数操作,对root.right执行函数操作,最终返回root即可
class Solution:
def trimBST(self, root: Optional[TreeNode], low: int, high: int) -> Optional[TreeNode]:
if root is None:
return
if root.val < low:
return self.trimBST(root.right,low,high)
if root.val > high:
return self.trimBST(root.left,low,high)
root.left = self.trimBST(root.left,low,high)
root.right = self.trimBST(root.right,low,high)
return root
每一条二叉树的「直径」长度,就是一个节点的左右子树的最大深度之和。
所以遍历一次二叉树,计算左右子树的最大深度和。
class Solution:
ret = 0 #深度和
def maxTree(self,root):
if root is None:
return 0
leftmax = self.maxTree(root.left)
rightmax = self.maxTree(root.right)
self.ret = max(leftmax+rightmax,self.ret) #更新最大和
anw = max(leftmax,rightmax) + 1 #递归的返回值
return anw
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
self.maxTree(root)
return self.ret
首先我们判断两棵树的节点值是否一样,然后再递归判断左子树是否一样,右子树是否一样,当p,q有一个是空的时候,退出递归,如果pq相等就是True否则就是False
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p is None or q is None:
return p is q
left_tree1 = self.isSameTree(p.left,q.left)
right_tree1 = self.isSameTree(p.right,q.right)
root_tree1 = True if p.val == q.val else False
return left_tree1 and right_tree1 and root_tree1
思想:是否轴对称就是看左右子树是否对称
class Solution:
def isSame(self,q,p):
if q is None or p is None:
return p is q
left_tree = self.isSame(q.right,p.left)
right_tree = self.isSame(p.right,q.left)
same = True if q.val == p.val else False
return left_tree and right_tree and same
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
if root is None:
return True
return self.isSame(root.left,root.right)
首先我们需要计算左右子树的高度,如果高度差大于1的时候,将高度设置为-1返回,并且每一个接口都不做改变,一直返回到递归入口。然后再调用该函数,如果函数的值是-1那就是不平衡,如果函数值不是-1,就是平衡
class Solution:
def getheight(self,root):
if root is None:
return 0
left_high = self.getheight(root.left)
if left_high == -1:
return -1
right_high = self.getheight(root.right)
if right_high == -1 or abs(right_high-left_high) > 1:
return -1
return max(left_high,right_high)+1
def isBalanced(self, root: Optional[TreeNode]) -> bool:
height = self.getheight(root)
return True if height > -1 else False
我们先遍历右子树,使用一个变量来记录当前右视图中的内容,如果层数(depth)等于当前记录的长度,就把该节点记录进去。然后再遍历左子树。
class Solution:
def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
ans = [] #存放右视图答案
def f(node,depth):
if node is None:
return
if depth == len(ans):
ans.append(node.val) #前序遍历,先放答案再遍历
f(node.right,depth+1) #右视图,先遍历右子树
f(node.left,depth+1)
f(root,0)
return ans
这道题同样是要求子树的最大和,我们用一个变量存储当前总的最大和:
class Solution:
def maxPathSum(self, root: Optional[TreeNode]) -> int:
ans = -inf
def f(node):
if node is None:
return 0
left_sum = f(node.left)
right_sum = f(node.right)
nonlocal ans
ans = max(left_sum + right_sum + node.val,ans)
return max(0,max(left_sum,right_sum) + node.val) #因为当前加上该节点可能是负数,所以还要和0比较一下
f(root)
return ans