二叉树系列2
- 101 对称二叉树
- 二叉树很重要的一点:确定遍历顺序
- 关于递归
- 代码随想录的代码
- 我的代码(理解后编写)
- 100 相同的树
- 我的代码
- 572 另一个树的子树
- 我的代码
- 录友的代码,只用递归!!!
- 104 二叉树的最大深度
- 重点
- 代码随想录的代码
- 我的代码(理解后自己写)
- 559 N 叉树的最大深度
- 代码随想录的代码
- 我的代码
- 111 二叉树的最小深度
- 代码随想录的代码
- 二刷要着重自己独立编写
- 222 完全二叉树的节点个数
- 代码随想录的代码
- 二刷要着重自己独立编写
- 110 平衡二叉树
- 我的代码
- 代码随想录的代码
- 257 二叉树的所有路径
- 重点
- 代码随想录的代码
- 此题很难,二刷时要注意
- 二刷要着重自己独立编写
- 二叉树系列2总结
101 对称二叉树
直接阅读的代码随想录的解答。
该用哪种遍历顺序?这很重要
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。其实后序也可以理解为是一种回溯,当然这是题外话,讲回溯的时候会重点讲的。
三种方法,递归,迭代队列,迭代栈。
难点在于递归终止条件的判断。
迭代法要注意:
在迭代法中使用了队列,需要注意的是这不是层序遍历,而且仅仅通过一个容器来成对的存放我们要比较的元素,知道这一本质之后就发现,用队列,用栈,甚至用数组,都是可以的。
二叉树很重要的一点:确定遍历顺序
需要收集孩子的信息,向上一层返回的题,要用后序遍历,左右中。
关于递归
这里写递归时可以发现,只要一个子递归函数返回了False,那么最终结果一定为False,没必要执行剩下的递归,但是递归函数中的return是无法做到强制跳出了,搜了一下,使用抛出异常的方式,跳出。
如果是Python语言,搜索了一下,网上给出的方法是:使用一个布尔型的全局变量。
代码随想录的代码
递归:
class Solution:def isSymmetric(self, root: TreeNode) -> bool:if not root:return Truereturn self.compare(root.left, root.right)def compare(self, left, right):#首先排除空节点的情况if left == None and right != None: return Falseelif left != None and right == None: return Falseelif left == None and right == None: return True#排除了空节点,再排除数值不相同的情况elif left.val != right.val: return False#此时就是:左右节点都不为空,且数值相同的情况#此时才做递归,做下一层的判断outside = self.compare(left.left, right.right) #左子树:左、 右子树:右inside = self.compare(left.right, right.left) #左子树:右、 右子树:左isSame = outside and inside #左子树:中、 右子树:中 (逻辑处理)return isSame
迭代法:使用队列
import collections
class Solution:def isSymmetric(self, root: TreeNode) -> bool:if not root:return Truequeue = collections.deque()queue.append(root.left) #将左子树头结点加入队列queue.append(root.right) #将右子树头结点加入队列while queue: #接下来就要判断这这两个树是否相互翻转leftNode = queue.popleft()rightNode = queue.popleft()if not leftNode and not rightNode: #左节点为空、右节点为空,此时说明是对称的continue#左右一个节点不为空,或者都不为空但数值不相同,返回falseif not leftNode or not rightNode or leftNode.val != rightNode.val:return Falsequeue.append(leftNode.left) #加入左节点左孩子queue.append(rightNode.right) #加入右节点右孩子queue.append(leftNode.right) #加入左节点右孩子queue.append(rightNode.left) #加入右节点左孩子return True
迭代法:使用栈
class Solution:def isSymmetric(self, root: TreeNode) -> bool:if not root:return Truest = [] #这里改成了栈st.append(root.left)st.append(root.right)while st:rightNode = st.pop()leftNode = st.pop()if not leftNode and not rightNode:continueif not leftNode or not rightNode or leftNode.val != rightNode.val:return Falsest.append(leftNode.left)st.append(rightNode.right)st.append(leftNode.right)st.append(rightNode.left)return True
层次遍历:
class Solution:def isSymmetric(self, root: TreeNode) -> bool:if not root:return Truequeue = collections.deque([root.left, root.right])while queue:level_size = len(queue)if level_size % 2 != 0:return Falselevel_vals = []for i in range(level_size):node = queue.popleft()if node:level_vals.append(node.val)queue.append(node.left)queue.append(node.right)else:level_vals.append(None)if level_vals != level_vals[::-1]:return Falsereturn True
我的代码(理解后编写)
递归:明确三部曲即可。
class Solution:def isSymmetric(self, root: Optional[TreeNode]) -> bool:if root == None :return Truereturn self.isequal(root.left,root.right)def isequal(self,left,right):if left == None and right == None :return Trueelif left != None and right == None :return Falseelif left == None and right != None :return Falseelif left.val != right.val :return Falseelse :isleft = self.isequal(left.left,right.right)isright = self.isequal(left.right,right.left)return isleft and isright
递归(强制退出,无法运行的错误代码):似乎不需要了解这种情况了
class Solution:def isSymmetric(self, root: Optional[TreeNode]) -> bool:if root == None :return Trueglobal judgejudge=Trueself.isequal(root.left,root.right,judge)return judgedef isequal(self,left,right,judge):if judge :if left == None and right == None :return Trueelif left != None and right == None :judge = Falsereturn Falseelif left == None and right != None :judge = Falsereturn Falseelif left.val != right.val :judge = Falsereturn Falseelse :isleft = self.isequal(left.left,right.right,judge)isright = self.isequal(left.right,right.left,judge)return isleft and isrightelse :return False
递归(强制退出,用self定义了类内的全局变量)
class Solution:def __init__(self):self.judge = Truedef isSymmetric(self, root: Optional[TreeNode]) -> bool:if root == None :return Trueself.isequal(root.left,root.right,self.judge)return self.judgedef isequal(self,left,right,judge):if self.judge :if left == None and right == None :return Trueelif left != None and right == None :self.judge = Falsereturn Falseelif left == None and right != None :self.judge = Falsereturn Falseelif left.val != right.val :self.judge = Falsereturn Falseelse :isleft = self.isequal(left.left,right.right,judge)isright = self.isequal(left.right,right.left,judge)self.judge = isleft and isrightreturn self.judgeelse :return False
迭代法:使用队列
编写要点:在此队列中,None是要加入队列的,如果left和right均为None,就continue。
from collections import deque
class Solution:def isSymmetric(self, root: Optional[TreeNode]) -> bool:if root == None :return Truedq = deque()dq.append(root.left)dq.append(root.right)while dq :left = dq.popleft()right = dq.popleft()if left == None and right == None :continueelif left != None and right == None : return Falseelif left == None and right != None :return Falseelif left.val != right.val :return Falseelse :dq.append(left.left)dq.append(right.right)dq.append(left.right)dq.append(right.left)return True
迭代法:使用栈
class Solution:def isSymmetric(self, root: Optional[TreeNode]) -> bool:if root == None :return Truest = []st.append(root.left)st.append(root.right)while st :left = st.pop()right = st.pop()if left == None and right == None :continueelif left != None and right == None : return Falseelif left == None and right != None :return Falseelif left.val != right.val :return Falseelse :st.append(left.left)st.append(right.right)st.append(left.right)st.append(right.left)return True
层次遍历,用deque
from collections import deque
class Solution:def isSymmetric(self, root: Optional[TreeNode]) -> bool:if root == None :return Truestack = deque()stack.append(root.left)stack.append(root.right)while stack :size = len(stack)if size % 2 != 0:return Falseres = []for i in range(size):node = stack.popleft()if node :res.append(node.val)stack.append(node.left)stack.append(node.right)else :res.append(None)if res != res[::-1] :return Falsereturn True
100 相同的树
我的代码
一刷只写了递归法,其他方法二刷再写
class Solution:def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:if p == None and q == None :return Trueelif p != None and q == None:return Falseelif p == None and q != None:return Falseelif p.val != q.val :return Falseelse :left = self.isSameTree(p.left,q.left)right = self.isSameTree(p.right,q.right)return left and right
572 另一个树的子树
我的代码
一刷虽然通过了,但方法是层次遍历+递归相等树判断,去遍历每一个节点,代码显得冗余
from collections import deque
class Solution:def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:if root == None and subRoot == None :return Trueelif root != None and subRoot == None:return Falseelif root == None and subRoot != None:return Falseelse :dq = deque()dq.append(root)while dq:size = len(dq)for i in range(size):node = dq.popleft()if node :judge = self.isSameTree(node,subRoot)if judge :return Truedq.append(node.left)dq.append(node.right)return Falsedef isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:if p == None and q == None :return Trueelif p != None and q == None:return Falseelif p == None and q != None:return Falseelif p.val != q.val :return Falseelse :left = self.isSameTree(p.left,q.left)right = self.isSameTree(p.right,q.right)return left and right
录友的代码,只用递归!!!
一个树是另一个树的子树,则:要么这两个树相等;要么这个树是左树的子树;要么这个树hi右树的子树
又因为提议可知,子树不为None,所以递归的判断环节可以简化。
但是这样只用递归,在时间消耗上比我的层次遍历稍高。
class Solution:def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool: if root == None :return Falseelse :jg1 = self.isSameTree(root,subRoot)jg2 = self.isSubtree(root.left,subRoot)jg3 = self.isSubtree(root.right,subRoot)return jg1 or jg2 or jg3def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:if p == None and q == None :return Trueelif p != None and q == None:return Falseelif p == None and q != None:return Falseelif p.val != q.val :return Falseelse :left = self.isSameTree(p.left,q.left)right = self.isSameTree(p.right,q.right)return left and right
104 二叉树的最大深度
用层次遍历的方法,是层次遍历的模板题,这里只学习使用递归的方法。
重点
搞清楚,深度和高度的区别。
高度:从上到下,3 2 1 。深度:从上到下:1 2 3 。
求高度,从下向上计数,为后序遍历,左右中,有一个中,就加一。求深度,从上向下计数,为前序遍历,中左右,有一个中,就加一。
根节点的高度,就是这颗二叉树的最大深度。
代码随想录的代码
后序遍历:(求高度)
class solution:def maxdepth(self, root: treenode) -> int:return self.getdepth(root)def getdepth(self, node):if not node:return 0leftheight = self.getdepth(node.left) #左rightheight = self.getdepth(node.right) #右height = 1 + max(leftheight, rightheight) #中return height
前序遍历:(求深度)
不推荐,不学习。二刷时再说。放一个Java的代码,着重看回溯的过程。
class Solution {
public:int result;void getDepth(TreeNode* node, int depth) {result = depth > result ? depth : result; // 中if (node->left == NULL && node->right == NULL) return ;if (node->left) { // 左depth++; // 深度+1getDepth(node->left, depth);depth--; // 回溯,深度-1}if (node->right) { // 右depth++; // 深度+1getDepth(node->right, depth);depth--; // 回溯,深度-1}return ;}int maxDepth(TreeNode* root) {result = 0;if (root == 0) return result;getDepth(root, 1);return result;}
};
我的代码(理解后自己写)
后序遍历:(求高度)
class Solution:def maxDepth(self, root: Optional[TreeNode]) -> int:if root == None :return 0left = self. maxDepth(root.left)right = self. maxDepth(root.right)return 1+max(left,right)
前序遍历:(求深度)
不推荐,不学习。二刷时再说。
559 N 叉树的最大深度
后序遍历,根节点的高度就是最大深度。
代码随想录的代码
class Solution:def maxDepth(self, root: 'Node') -> int:if not root:return 0max_depth = 1for child in root.children:max_depth = max(max_depth, self.maxDepth(child) + 1)return max_depth
我的代码
class Solution:def maxDepth(self, root: 'Node') -> int:if root == None :return 0res = []n = len(root.children)if n==0 :return 1else :for i in root.children :res.append(self.maxDepth(i))depth = 1 + max(res)return depth
111 二叉树的最小深度
用层次遍历的方法,是层次遍历的模板题,这里只学习使用递归的方法。
同样使用后续遍历。
此题一定要注意最小深度的定义,是叶子节点到根节点的最短距离,而叶子节点的定义为:左右孩子均为None 。
很容易错的一道题,没弄明白之前,递归逻辑也不好写。
注意避开陷阱!当只有一个孩子为None时,返回的是 1+不为空子树的最小高度。
代码随想录的代码
后序+递归:
class Solution:def getDepth(self, node):if node is None:return 0leftDepth = self.getDepth(node.left) # 左rightDepth = self.getDepth(node.right) # 右# 当一个左子树为空,右不为空,这时并不是最低点if node.left is None and node.right is not None:return 1 + rightDepth# 当一个右子树为空,左不为空,这时并不是最低点if node.left is not None and node.right is None:return 1 + leftDepthresult = 1 + min(leftDepth, rightDepth)return resultdef minDepth(self, root):return self.getDepth(root)
前序+递归:这里用 init 定义一个self的全局变量的思路,很值得学习。隐藏了回溯的思想,每次递归时,并未改变depth。
class Solution:def __init__(self):self.result = float('inf')def getDepth(self, node, depth):if node is None:returnif node.left is None and node.right is None:self.result = min(self.result, depth)if node.left:self.getDepth(node.left, depth + 1)if node.right:self.getDepth(node.right, depth + 1)def minDepth(self, root):if root is None:return 0self.getDepth(root, 1)return self.result
二刷要着重自己独立编写
这里写的就不如代码随想录给出的标准答案,调用递归次数过多,就应该在第一个 if 判断后,就去统计左右深度。
class Solution:def minDepth(self, root: Optional[TreeNode]) -> int:if root == None :return 0if root.left == None and root.right == None :return 1elif root.left == None :return 1 + self.minDepth(root.right)elif root.right == None :return 1 + self.minDepth(root.left)else :return 1 + min(self.minDepth(root.left),self.minDepth(root.right))
222 完全二叉树的节点个数
迭代法依然是层序遍历的蓝本。递归法也好理解,递归的目标是左右子树的节点个数。值得学习的是,利用完全二叉树的性质,来写出时间复杂度更低的代码。
递归,依然是后序遍历,本质上和其高度类似,只不过递归逻辑由求高度,变为了求数量。
代码随想录的代码
递归:
class Solution:def countNodes(self, root: TreeNode) -> int:return self.getNodesNum(root)def getNodesNum(self, cur):if not cur:return 0leftNum = self.getNodesNum(cur.left) #左rightNum = self.getNodesNum(cur.right) #右treeNum = leftNum + rightNum + 1 #中return treeNum
利用完全二叉树:
class Solution:def countNodes(self, root: TreeNode) -> int:if not root:return 0left = root.leftright = root.rightleftDepth = 0 #这里初始为0是有目的的,为了下面求指数方便rightDepth = 0while left: #求左子树深度left = left.leftleftDepth += 1while right: #求右子树深度right = right.rightrightDepth += 1if leftDepth == rightDepth:return (2 << leftDepth) - 1 #注意(2<<1) 相当于2^2,所以leftDepth初始为0return self.countNodes(root.left) + self.countNodes(root.right) + 1
二刷要着重自己独立编写
利用完全二叉树:
class Solution:def countNodes(self, root: Optional[TreeNode]) -> int:if root == None :return 0leftnode = root.leftrightnode = root.rightln = 1rn = 1while leftnode :leftnode = leftnode.leftln += 1while rightnode :rightnode = rightnode.rightrn += 1if ln == rn :return 2**ln - 1else :return 1+self.countNodes(root.left)+self.countNodes(root.right)
110 平衡二叉树
使用递归的思想编写,不用迭代方法,首先此题不能用层序遍历,迭代法无法很好地模拟回溯过程,中间存在很多重复计算。
具体分析参考代码随想录的文章即可。
平衡二叉树
我的代码
发现,我现在写递归,很喜欢加一个全局变量来做判断了,也不知道是好是坏。
所以还是学习一下的代码随想录的解答方式。
class Solution:def __init__(self):self.judge = Truedef isBalanced(self, root: Optional[TreeNode]) -> bool:self.digui(root)return self.judgedef digui(self,root):if root == None :return 0if self.judge :left = self.digui(root.left)right = self.digui(root.right)if abs(left-right) > 1 :self.judge = Falsereturn 1 + max(left,right)else :return 0
代码随想录的代码
其实对递归了解较深之后,这样一层一层返回-1,和搞一个self变量是一样的效果!!!
所以还是学习下面这种写法吧。
class Solution:def isBalanced(self, root: TreeNode) -> bool:if self.get_height(root) != -1:return Trueelse:return Falsedef get_height(self, root: TreeNode) -> int:# Base Caseif not root:return 0# 左if (left_height := self.get_height(root.left)) == -1:return -1# 右if (right_height := self.get_height(root.right)) == -1:return -1# 中if abs(left_height - right_height) > 1:return -1else:return 1 + max(left_height, right_height)
257 二叉树的所有路径
第一道,递归+回溯的题目!!!
回溯一直是没有掌握的点!!!
直接学习代码随想录的解答。文章链接如下。
二叉树的所有路径
重点
同样需要先确定遍历顺序,肯定为前序遍历,因为输出路径为父节点指向子节点,中序和后序,无法得到父节点到子节点的指向。
所谓隐藏回溯,就是对Path做拷贝操作,这样在后序递归时,对Path的修改,不会影响到上一层的其他带Path的语句,所以不需要回溯操作。
对于Python来说,对同一个变量进行操作,所有的地方都会发生改变,类似于C语言中对地址的操作,想要不改变原变量,就要用copy()操作。
代码随想录的代码
递归法+回溯
class Solution:def traversal(self, cur, path, result):path.append(cur.val) # 中if not cur.left and not cur.right: # 到达叶子节点sPath = '->'.join(map(str, path))result.append(sPath)returnif cur.left: # 左self.traversal(cur.left, path, result)path.pop() # 回溯if cur.right: # 右self.traversal(cur.right, path, result)path.pop() # 回溯def binaryTreePaths(self, root):result = []path = []if not root:return resultself.traversal(root, path, result)return result
递归法+隐形回溯(版本一)
from typing import List, Optionalclass Solution:def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:if not root:return []result = []self.traversal(root, [], result)return resultdef traversal(self, cur: TreeNode, path: List[int], result: List[str]) -> None:if not cur:returnpath.append(cur.val)if not cur.left and not cur.right:result.append('->'.join(map(str, path)))if cur.left:self.traversal(cur.left, path[:], result)if cur.right:self.traversal(cur.right, path[:], result)
递归法+隐形回溯(版本二)
class Solution:def binaryTreePaths(self, root: TreeNode) -> List[str]:path = ''result = []if not root: return resultself.traversal(root, path, result)return resultdef traversal(self, cur: TreeNode, path: str, result: List[str]) -> None:path += str(cur.val)# 若当前节点为leave,直接输出if not cur.left and not cur.right:result.append(path)if cur.left:# + '->' 是隐藏回溯self.traversal(cur.left, path + '->', result)if cur.right:self.traversal(cur.right, path + '->', result)
迭代法(有些难理解的)
class Solution:def binaryTreePaths(self, root: TreeNode) -> List[str]:# 题目中节点数至少为1stack, path_st, result = [root], [str(root.val)], []while stack:cur = stack.pop()path = path_st.pop()# 如果当前节点为叶子节点,添加路径到结果中if not (cur.left or cur.right):result.append(path)if cur.right:stack.append(cur.right)path_st.append(path + '->' + str(cur.right.val))if cur.left:stack.append(cur.left)path_st.append(path + '->' + str(cur.left.val))return result
此题很难,二刷时要注意
二刷要着重自己独立编写
递归法+回溯
class Solution:def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:if root == None :return Noneres = []path = []self.digui(root,path,res)return resdef digui(self,cur,path,res):path.append(cur.val)if cur.left == None and cur.right == None :sPath = '->'.join(map(str, path))res.append(sPath)return if cur.left :self.digui(cur.left,path,res)path.pop()if cur.right :self.digui(cur.right,path,res)path.pop()
二叉树系列2总结
直接放代码随想录总结文章的链接。
二叉树系列2总结
回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!
讲了这么多二叉树题目的迭代法,有的同学会疑惑,迭代法中究竟什么时候用队列,什么时候用栈?
如果是模拟前中后序遍历就用栈,如果是适合层序遍历就用队列,当然还是其他情况,那么就是 先用队列试试行不行,不行就用栈