leetcode_二叉树篇_python
主要是深度遍历和层序遍历的递归和迭代写法。
另外注意:因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)。
所有题目首先考虑root否是空。有的需要考虑root是否是范围内合理的起点。其他细节:每次需要引用节点值时考虑是否非空。
基本概念:
- 二叉树节点的深度:从上数第几层:指从根->该节点的最长简单路径边的条数。
- 二叉树节点的高度:从下数第几层:指从节点->叶子节点的最长简单路径边的条数。
leetcode144.二叉树的前序遍历
递归较容易,迭代即每次访问stack的top,把左右非空的压进去即可注意左右入栈顺序
leetcode145.二叉树的后序遍历
递归类似。迭代直接写较复杂,转换为先序(左右调换),结果倒序输出。
leetcode94.二叉树的中序遍历
递归类似前序。
迭代时因为访问和路过不同,所以用cur做路线,stack做访问,stack以空开始,cur先把最左一路全部压入,再pop读值,然后cur转到右边第一个。
迭代容易写错
可以while嵌套while
stack = []
cur = root while cur or stack: while cur: stack.append(cur) cur = cur.left cur = stack.pop() nums.append(cur.val) cur = cur.right
也可以用while (if else)
stack = [] cur = root while cur or stack: if cur: stack.append(cur) cur = cur.left else: cur = stack.pop() nums.append(cur.val) cur = cur.right
leetcode102.二叉树的层序遍历
用双端队列。注意点:队列先入先出,故左先右后。此外,要先看deque的长度再循环,因为其长度是变化的。
while deque_tree: len_que = len(deque_tree) #注意 res_temp = [] for i in range(len_que): cur = deque_tree.popleft() res_temp.append(cur.val) if cur.left != None: #注意 deque_tree.append(cur.left) if cur.right != None: deque_tree.append(cur.right)
类似题目:107,199,637,429, 104, 559,513
leetcode226.翻转二叉树
即将二叉树每个点的的左右孩子交换。要点:每个点都要遍历到且只到一次。
建议使用前/后序遍历和层序遍历,简单且直观。不能使用中序遍历。
leetcode101.对称二叉树
判断左右子树是否对称。此题deque中可以存在空,这样不用单独判断第一个节点,处理比较方便。所以在循环中需要根据空/非空判断,重点是处理左右均是空时不更新deque,至少有一个非空时判断是否对称;全部非空才更新deque。
关键代码:
stack = [] stack.append(root.left) stack.append(root.right) while stack: cur_right = stack.pop() cur_left = stack.pop() if (not cur_left) and (not cur_right): continue if ((not cur_left) and cur_right) or ((not cur_right) and cur_left) or cur_right.val != cur_left.val: return False stack.append(cur_left.left) #注意cur_left和cur_right顺序 与pop一致 stack.append(cur_right.right) stack.append(cur_left.right) stack.append(cur_right.left)
leetcode104.二叉树最大深度
迭代法:层序遍历
递归法:节点深度等于左右子树深度中最大的加1
类似题目:559 n叉树最大深度
leetcode111.二叉树最小深度
迭代法:层序遍历。注意每到新的一层直接更新1,遇到叶子节点return
while deque_tree: len_que = len(deque_tree) min_dep += 1 #注意 for i in range(len_que): cur = deque_tree.popleft() if (not cur.left) and (not cur.right): return min_dep if cur.left: deque_tree.append(cur.left) if cur.right: deque_tree.append(cur.right)
递归:不能到None返回,子树为空时深度为0,但不合题因为非叶子节点。所以可以把0去掉:
def mindep(root): if not root: return 0 left = mindep(root.left) right = mindep(root.right) if left == 0 or right == 0: return max(left, right)+1 else: return min(left,right)+1
leetcode110.平衡二叉树
要求判断二叉树是否平衡,即左子树深度和右子书深度差不大于1。
因为是依靠深度判断,所以是后序遍历。递归返回深度,如果不平衡则返回-1标记(注意保持有一个是-1即返回-1)
def traverse(root): if not root: return 1 leftdepth = traverse(root.left) rightdepth = traverse(root.right) if leftdepth==-1 or rightdepth==-1 or abs(rightdepth-leftdepth)>1: return -1 else: return max(leftdepth, rightdepth)+1
leetcode257.找所有的路径
要求找二叉树根到所有叶子节点的路径。需要使用回溯,但还没学到,以后可以重新审视。
迭代法:遍历节点,放在stack中的元素为元组(root, path_temp),后者记录到该节点的路径。注意更新stack时要将数值转为str再合并。
递归法:要想清楚架构。递归不反回值,主要通过递归修改res。每个点需要的参数有root, 上层路径path及需要修改的res。每次遇到叶子节点时进行修改。
def getpaths(self, root, path,res): if (not root.left) and (not root.right): res_temp = '' for i in range(len(path)): res_temp += str(path[i].val)+'->' res_temp += str(root.val) res.append(res_temp) return else: if root.left: #注意 self.getpaths(root.left,path+str(root.val),res) if root.right: #注意 self.getpaths(root.right,path+str(root.val),res)
leetcode112.路径总和
判断跟->叶子节点的路径和中是否有等于给定目标值的。
迭代法:跟257类似,stack中元素改为(root, sums),判断是否是叶子节点。
递归法:和257类似。返回bool_left or bool_right,因为有一个存在即有。注意如果left是空时,为False,right同理。
leetcode113.路径总和II
找到所有路径和等于目标值的路径。是112和257的结合。
迭代法:同257,只是每次到叶子节点需要判断一下再存。
递归法:同257,注意path传入时不要给path赋值,因为是list所以相当于“指针”,不要修改。
leetcode100.是否是相同的树
迭代递归均可。主要注意空非空的考虑。
leetcode572.是否是子树
先对到根节点,再套用leetcode100判断是否相同。
leetcode404.左叶子之和
判断:1.是叶子节点。2:是左叶子。因此需要上一个节点,或上一个节点的信息。
递归和迭代均即可。迭代时存上一个节点不好存,所以可以放在stack中:(root,bool_isleft)来指示。注意取出时需要分别把root和boil_isleft取出
leetcode106.从中序和后序序列构造二叉树
递归法:从后序序列的最后一个确定中间节点,切割递归。注意取出序列时用pop.
需要方法:list.index()确定索引值的索引
leetcode105.从中序与前序序列构造二叉树
同106。拓展:如只有前和后序,能否构造二叉树?不能。信息量一样,都只能确定中间,没法确定左右顺序。举例可举两个关于镜像的二叉树
类似题目 654
leetcode617. 合并二叉树
将两个二叉树合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
递归较容易。
迭代:stack中均非空,按其左右分类讨论
leetcode700.二叉搜索树寻值
迭代:利用二叉搜索树的结构。
递归:在确定向左还是向右时注意谁是被寻的值
leetcode98.验证二叉搜索树
即验证中序遍历或序列是否是递增的。
leetcode530.BST的最小绝对差
BST类题目的中点是相当于在递增数组上操作,因为其中序遍历即是递增数组。因此该题可以中序遍历BST,算绝对值差
易错点:需要记录上一个节点,每次都要记录。而在判断值时才需要判断上一个节点是否非空。此外,中序遍历的迭代写法的最后一步直接写cur = cur.right, 因为在开头会顾忌其是否非空的判断,无需多想。
leetcode501.BST的众数
虽然本题是求BST的众数,作为拓展,分为求BST众数和一般二叉树众数的求解。
对BST:依然是考虑为递增数组,需判断与上一个节点是否相同来决定计数。当数值等于最大时,加入结果中;数值大于最大时,将结果清空(list.clear()),再加入结果。
对一般二叉树:遍历数组,把结果存到字典中,key:value=number:count。重点在对字典排序。
dict_tree_sorted = sorted(dict_tree.items(), key = lambda x:x[1], reverse = True)
三个要点:1.函数sorted。2.将字典的元素的集合变成列表,列表中的元素形式为元组:dict.items()。3.隐函数确定key key = lambda x:x[1]。lambda后无冒号。
此外,在循环中,取字典中的值为dict.get()
leetcode236.最近的公共祖先
该题有一定难度,需要复习!需要复习!需要复习!
题目明确,两个元素都在树中存在(如果没有该限制,则考虑加全局的bool判断是否全部存在)。因为最近的公共祖先(即最低的,翻译问题)最低,所以在遍历时应从底向上遍历,因此考虑后序遍历。如果是空,则返回空;是两元素之一,则返回两元素之一。在处理中间节点时,分为两种情况:1.若左右均空,则返回空。2.若有至少一个非空:若两个均非空,则返回root;如有一个非空,则返回非空的。因此,如果两个在两支上,则返回连接点;若在同一支,则返回较高的。另一个角度,在高处只有一支有,所以只有一支的情况包含了两元素在同一支的情况和遍历到较高点的情况。
class Solution: def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': if not root or root == q or root == p: return root left_node = self.lowestCommonAncestor(root.left, p, q) right_node = self.lowestCommonAncestor(root.right, p, q) if left_node or right_node: if left_node and right_node: return root else: return left_node or right_node else: return None
leetcode235.BST最近的祖先
不需要像一般二叉树需要从底层向上遍历。从上到下遍历时,只要当前值在两元素区间内(注意是闭区间),则该点即是要找的节点。
leetcode701.BST插入节点
直接插到符合条件的空节点处即可。从root开始,按大小关系找到空节点,每一步要记录上一节点。
leetcode450.BST删除节点
删除节点有很多细节需要处理。首先从整体思路上考虑,删除节点时首先判断该节点的子树是单支还是双支:如果是单支,则直接把单支并到前一个节点上;如果是双支,则需要把左支并到右支的最左边(因为所有右边的元素均大于左边),再将右支并到左支。注意到,需要前一个节点,则细节1:需要删除的是根节点时,则前一个节点为空,需要单独处理。易错点:该题分类比较麻烦,最后分完类要记得return。写完检查一下每一类是否都有return对应的情况。
leetcode669.BST减枝
修剪BST,使得BST的节点值都在[low high]范围内。一个常见的错误思路是找到一个区间内的点作为root起点,如果左节点小于low就直接将左支删掉,右节点类似。产生该错误的主要原因是虽然左支的数都比中间的小,注意是比中间的小,而不是比low小,直接删除左支不可取,右支同理。
正确的思路是:找到一个合乎区间的点作为root。先处理左枝:如果左支小于low,则删除比左支小的部分,即将左节点替换为左节点的右节点(注意不是根的右节点),继续判断。如果左节点在区间内,则将cur移到左节点,重复刚才的判断——摸石头过河,一步一步拓展。右枝的处理同理。
易错点:在找合乎区间的root时,注意如果该点比low小,则向右。方向容易搞错。
leetcode108.有序数组构造BST
每次将中间的元素构造节点,左枝等于中间左边部分,右枝等于中间右边部分。
leetcode538.BST转化为累加树
将BST每个节点的值转化为大于等于其值的和。因为是大于等于,所以相当于是本身的值加上右边所有节点的值(包括父节点和右子树),因此即右中左遍历,每个节点的值等于该节点的值加上上一节点的值(因为上一节点的值已经是之前的和)即可。