JZ36 二叉搜索树与双向链表
解法1:进行二叉树的中序遍历,并将遍历结果存储到数组中,再对数组进行遍历生成双向链表
class Solution:
def Convert(self , pRootOfTree ):
# write code here
if not pRootOfTree:
return
res = []
def order(root, res):
if root.left:
order(root.left, res)
res.append(root)
if root.right:
order(root.right, res)
tree = order(pRootOfTree, res)
if len(res) > 1:
for ti in range(len(res)-1):
res[ti + 1].left = res[ti]
res[ti].right = res[ti + 1]
return res[0]
空间复杂度O(N):辅助数组占用额外空间。不符合题目要求
时间复杂度O(N):N表示二叉树结点数量,遍历二叉树O(N),遍历数组O(N)
解法2:边中序遍历,边生成双向链表,不保存遍历结果
时间复杂度,中序遍历一遍二叉树,每个节点都只被访问了一次,故时间复杂度为O(n),n为节点数量
空间复杂度,定义了两个辅助指针pre, ans,故为O(1)
class Solution:
def Convert(self , p):
self.pre=None #pre含义同上述图片代码
self.ans=None #ans为双向链表表头
self.inOrder(p) #中序遍历,线索化二叉树
return self.ans #返回答案,,最左端的节点,为双向链表的表头
def inOrder(self,p): #中序遍历
if p==None: #如果是空树的情况
return
self.inOrder(p.left)#递归调用,左子树线索化
if p.left==None:#同上述图片中的代码,前驱线索化
if self.ans==None:#第一个找到的左子树为空的节点,为双向链表的表头
self.ans=p#答案即中序遍历最左端的节点
p.left=self.pre#前驱线索化
if self.pre!=None:#如果前驱节点存在
self.pre.right=p#因为链表是双向的,建立前驱节点的后继线索
if self.pre!=None and self.pre.right==None:#同上述图片中的代码,后继线索化
self.pre.right=p#建立前驱节点的后继线索
p.left=self.pre#建立后继节点的前驱线索
self.pre=p
self.inOrder(p.right)#递归调用,右子树线索化
注:
- 在构建双向链表时,每个节点肯定会出现p.left == None或if self.pre!=None and self.pre.right==None中的一种情况,所以用这两个条件。并在每个条件下,都构建节点的双向连接(即进行left=?和right=?操作)。
- 不必在意构建链表时会有节点没有指针指,地址丢失的情况。因为一直有指针p指向当前节点。
JZ79 判断是不是平衡二叉树
解法1:自顶向下
class Solution:
def IsBalanced_Solution(self , pRoot: TreeNode) -> bool:
# write code here
if not pRoot:
return True
return self.IsBalanced_Solution(pRoot.left) and self.IsBalanced_Solution(pRoot.right) \
and abs(self.depth(pRoot.left) - self.depth(pRoot.right)) <= 1
def depth(self, root):
if not root:
return 0
return max(self.depth(root.left), self.depth(root.right)) + 1
时间复杂度 O(Nlog2N):最差情况下,需要遍历树所有节点判断是否平衡,需要O(N)。判断每个节点最 大高度需要递归左右子树,需要占用 O(log2N),所以总共占用O(Nlog2N)
空间复杂度O(N):最差情况下(树退化为链表时),递归需要使用O(N) 的栈空间。(不李姐)
解法2:自底向上递归:
解法1在「计算二叉树高度」时遍历了树的结点,在「判断是否平衡」时又遍历了一次树的结点,因此产生了重复计算。针对解法一的优化是采用「自底向上」的解题思路:
从最底的叶子结点开始,计算该结点的高度。若该结点不平衡,则直接返回False,不用继续计算其他结点高度,否则返回其高度;
若自底向上的过程中一直都是平衡的,则最终的树是平衡的。
此方法每个结点(最坏时)仅会遍历一次,不会有重复计算。
class Solution:
def IsBalanced_Solution(self , pRoot: TreeNode) -> bool:
# write code here
if not pRoot:
return True
if not self.IsBalanced_Solution(pRoot.left):
return False
if not self.IsBalanced_Solution(pRoot.right):
return False
if abs(self.depth(pRoot.left) - self.depth(pRoot.right)) > 1:
return False
return True
def depth(self, root):
if not root:
return 0
return max(self.depth(root.left), self.depth(root.right)) + 1
时间复杂度O(N):递归最差情况下(树退化为链表时),需要使用 O(N) 的栈空间。(不李姐)
空间复杂度O(N):递归需要使用额外的树节点数量的堆栈空间
JZ8 二叉树的下一个结点
Q1:首先问你一个问题,如果这道题出现在笔试题中,你会用什么方法做?如果出现在面试题中呢?
A1:我想你肯定有点疑惑,同一道题为什么还分出现在笔试题中还是面试题中呢?很显然,笔试题中只要能过就好,设计的算法丑点,慢点也无所畏,不一定需要最优解法,当然前提是能够通过。而面试中就不一样了,显然面试官希望听到最优解法。
A2,网友回答:我一开始也是想用方法二,但发现总是漏掉一些情况。所以说要是做笔试,最好就直接用方法一,递归遍历省时省力还不容易错。面试要是手撕这道,最好先跟hr说方法一,然后再表明:其实我还有一种更好的方法…之后再尝试写方法二
解法1:暴力解法
- 根据给出的结点求出整棵树的根节点
- 根据根节点递归求出树的中序遍历,存入vector
- 在vector中查找当前结点,则当前结点的下一结点即为所求。
tyy看了题解后,自己写的,改了几个小bug后成功通过!和答案写法一样
class Solution:
def GetNext(self, pNode):
# write code here
now = pNode
while pNode.next:
pNode = pNode.next
vector = []
self.vec(pNode, vector)
now_locat = vector.index(now)
if now_locat < len(vector) - 1:
return vector[now_locat + 1]
return
def vec(self, root, l):
if root.left:
self.vec(root.left, l)
l.append(root)
if root.right:
self.vec(root.right, l)
- 时间复杂度:第一步:最坏为O(N), N为整棵树结点的个数。第二步:O(N), 第三步:最坏为O(N), 所以整的时间复杂度:3*O(N)
- 空间复杂度:O(N)
解法2:最优解法
分两种情况:
1、如果目标结点不是叶子结点
则在中序遍历中,下一个结点为当前结点的右子树的最左叶子结点
循环遍历,找到当前子树的最左叶子结点
2、如果目标结点是叶子结点而不是根节点,则需要向上寻找父节点
结点是父节点的左子结点,则在中序遍历中,目标结点的下一个结点就是父节点
结点是是父节点的右子结点,则在中序遍历中,还要继续寻找, 直到满足当前结点是父节点的左子结点
先看tyy的错误解法(运行超时):
class Solution:
def GetNext(self, pNode):
# write code here
if pNode.right: # 如果有右孩子
right = pNode.right
while right:
if right.left:
right = right.left
if not right.left:
return right
else: # 没有右孩子
pre = pNode.next
if pre:
if pNode == pre.left: # 如果是左孩子,则返回父节点
return pre
# 下面错了。因为只要是没有右孩子就一直往上寻找左孩子的父节点,if和else可以合并成一种情况
else: # 如果是右孩子,则返回回溯左节点的父节点
while pre:
if pre.next.left:
if pre.next.left == pre:
return pre.next
else:
pre = pre.next
return
正确解法:
class Solution:
def GetNext(self, pNode):
# write code here
if pNode.right: # 如果有右孩子
right = pNode.right
while right:
if right.left:
right = right.left
if not right.left:
return right
else: # 如果没有右孩子
while pNode.next:
pre = pNode.next
if pNode == pre.left:
return pre
pNode = pre
return
时间复杂度 O(N):最坏的情况是遍历所有结点
空间复杂度 O(1):没有用到类似数组的类型数据
JZ28 对称的二叉树
解法1:迭代
tyy的解法:
class Solution:
def isSymmetrical(self , pRoot: TreeNode) -> bool:
# write code here
if not pRoot:
return True
l_list = []
r_list = []
l_list.append(pRoot)
r_list.append(pRoot)
while l_list:
now_l = l_list.pop(0)
now_r = r_list.pop(0)
if now_l.left:
if not now_r.right:
return False
else:
if now_l.left.val != now_r.right.val: # 注意是判断val相等而不是判断节点相等(不是if now_l.left != now_r.right)
return False
l_list.append(now_l.left)
r_list.append(now_r.right)
if now_l.right:
if not now_r.left:
return False
else:
if now_l.right.val != now_r.left.val:
return False
l_list.append(now_l.right)
r_list.append(now_r.left)
return True
解法2:递归
class Solution:
def isSymmetrical(self , pRoot: TreeNode) -> bool:
# write code here
if not pRoot:
return True
def same(root1, root2):
if not root1 and not root2:
return True
if not root1 or not root2:
return False
if root1.val != root2.val:
return False
return same(root1.left, root2.right) and same(root1.right, root2.left)
return same(pRoot.left, pRoot.right)
JZ78 把二叉树打印成多行
解法 1:迭代(BFS,广度优先遍历)
class Solution:
def Print(self , pRoot: TreeNode) -> List[List[int]]:
# write code here
result = []
if not pRoot:
return result
l = []
l.append(pRoot)
while l:
size = len(l)
num = []
for i in range(size): # 这句机智的代码我没想出来
now = l.pop(0)
num.append(now.val)
if now.left:
l.append(now.left)
if now.right:
l.append(now.right)
result.append(num)
return result
时间复杂度:O(N) 空间复杂度:O(N)
解法2:递归
其实我们层序遍历一般不会用递归实现,但是这道题比较特殊,特殊之处在于,保存层数的 deep 可以索引出对应的层信息,方便结点直接找到同一层其他结点
我们考虑的问题就变成如何让同一层结点从左到右依次打印,第一个要点肯定是保证入队的顺序,先左子树,后右子树,再一个要点是需要保证建立 list 的顺序,即上一层的 list 先建立先存储进 lists,综合考虑,选用前序遍历的模板
class Solution:
# 返回二维列表[[1,2],[4,5]]
def Print(self, pRoot):
# write code here
result = []
def align(root, depth):
if root:
if len(result)<depth:
result.append([])
result[depth-1].append(root.val)
align(root.left, depth + 1)
align(root.right, depth + 1)
align(pRoot,1)
return result
时间复杂度:O(n)
空间复杂度:O(n)
JZ37 序列化二叉树
0. 再再重复一遍None和空字符串/列表/…的区别:添加链接描述
0.1:类型不同:type(None)类型为 <class ‘NoneType’>, type(‘’)为<class '‘str’>
0.2:判断的时候均为false:所以not ~为True
解法1:前序遍历
思路(官方题解法):利用二叉树递归,序列化即将二叉树的节点值取出,放入一个字符串中,我们可以按照前序遍历的思路,遍历二叉树每个节点,并将节点值存储在字符串中,我们用‘#’表示空节点,用‘!'表示节点与节点之间的分割。
反序列化即根据给定的字符串,将二叉树重建,因为字符串中的顺序是前序遍历,因此我们重建的时候也是前序遍历,即可还原。
import sys
#设置递归深度
sys.setrecursionlimit(100000)
class Solution:
def __init__(self):
self.index = 0
self.s = ""
#处理序列化(递归)
def SerializeFunction(self, root):
#空节点
if not root:
self.s += '#'
return
#根节点
self.s += (str)(root.val) + '!'
#左子树
self.SerializeFunction(root.left)
#右子树
self.SerializeFunction(root.right)
def Serialize(self, root):
if not root:
return '#'
self.s = ""
self.SerializeFunction(root)
return self.s
#处理反序列化的功能函数(递归)
def DeserializeFunction(self, s: str):
# 到达叶节点时,构建完毕,返回继续构建父节点
#空节点
if s[self.index] == "#":
self.index += 1
return None
# 数字转换
val = 0 (因为数字都是以字符串的形式存储的,如23:需要先算出十位的2,然后用2*10+3)
#遇到分隔符或者结尾
while s[self.index] != '!':
val = val * 10 + (int)(s[self.index])
self.index += 1
root = TreeNode(val)
#序列到底了,构建完成
self.index += 1
#反序列化与序列化一致,都是前序
root.left = self.DeserializeFunction(s)
root.right = self.DeserializeFunction(s)
return root
def Deserialize(self, s):
if s == "#":
return None
return self.DeserializeFunction(s)
看看tyy默写的:
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def __init__(self) -> None:
self.index = 0
self.s = ""
def SerializeFunction(self, root):
if not root:
self.s += "#"
# return ""
return # 第二处修改,可以省略""
if root:
self.s += str(root.val)
self.s += "!"
# self.s += self.SerializeFunction(root.left)
# self.s += self.SerializeFunction(root.right)
self.SerializeFunction(root.left)
self.SerializeFunction(root.right) # 第三处修改,因为if root 为True时,self.SerializeFunction(root.left)没有返回值,所以不必写成self.s +=
def Serialize(self, root):
if not root:
return "#" # 第一处修改,空树直接返回"#"
# return
self.SerializeFunction(root)
# write code here
return self.s
def DeserailizeFuction(self, s):
val = 0
while (s[self.index] != "#" and s[self.index] != "!"):
val = val * 10 + int(s[self.index])
self.index += 1
# self.index += 1 # 第4处修改,因为需要先索引s[self.index],所以不能先将self.index += 1,应该索引完了再加
# if s[self.index] == "#":
# return
if s[self.index] == "#":
self.index += 1
return
node = TreeNode(val)
self.index += 1
node.left = self.DeserailizeFuction(s)
node.right = self.DeserailizeFuction(s)
return node
def Deserialize(self, s):
if s == "#":
return
return self.DeserailizeFuction(s)
解法1.1:一种较简单的前序遍历
class Solution:
def __init__(self) -> None:
self.s = []
self.index = 0
def Serialize(self, root):
# write code here
if not root:
self.s.append("#")
return
self.s.append(root.val)
self.Serialize(root.left)
self.Serialize(root.right)
return self.s
def Deserialize(self, s):
# write code here
if not s: # 不知道为啥,不加这个判断会报错,改成if s == ["#"]判断也报同样的错。明明def Serialize规定了空树返回["#"]
return
if s[self.index] == "#" :
self.index += 1
return
node = TreeNode(s[self.index])
self.index += 1
node.left = self.Deserialize(s)
node.right = self.Deserialize(s)
return node
解法2:层次遍历BFS
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def Serialize(self, root): # 把树的值按顺序保存为str
if not root:
return
nodeset = []
res = []
nodeset.append(root)
while nodeset:
now = nodeset.pop(0) # pop() 用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值
if now == '#': # 修改3,因为下面的代码在遇到空节点时还会往res中添加'#',所以在这里需要判断是不是空节点
res.append('#')
continue
res.append(str(now.val)) # 修改1,忘了添加now的值到res中
if now.left:
nodeset.append(now.left)
# res.append(str(now.left.val)) # 修改2,不应该在这里就把left和right的值加到res中。应该是当把这儿元素pop出来的时候再往res中添加它的val
else:
nodeset.append("#") # 修改6,注意是往nodeset中添加节点,不要把nodeset写成res
if now.right:
nodeset.append(now.right)
# res.append(str(now.right.val))
else:
nodeset.append("#")
for i in range(len(res)-1, -1, -1):
if res[i] != "#":
break
yes = ",".join(res[:i+1]) # list[索引]:返回还是list
return yes
# write code here
def Deserialize(self, s):
if not s:
return
strin = s.split(",") # split: 返回列表
nodeset = []
now = strin.pop(0)
root = TreeNode(int(now))
nodeset.append(root)
while strin:
now = strin.pop(0) # 存节点值
nownode = nodeset.pop(0) # 存节点
if now != "#":
nownode.left = TreeNode(int(now))
nodeset.append(nownode.left) # 修改4,忘了将left节点加入到nodeset中
if strin: # 修改5,先判断strin中是否还有元素值
now = strin.pop(0)
if now != "#":
nownode.right = TreeNode(int(now))
nodeset.append(nownode.right)
return root
# write code here
JZ84 二叉树中和为某一值的路径(三)
解法1: 两次遍历
先看看tyy只看了解题思路但没看代码之后自己写的代码:
class Solution:
def __init__(self) -> None:
self.num = 0
def dfs(self, root, sum):
if not root:
return
while sum: # 错误1:if 0为False,if not 0为True,而sum和节点的值均可以为0,所以这个循环条件使用错误;另外,如果在while循环下有一个条件不满足任何while下的if判断,while还是会继续执行,因为只要while的条件为True就会一直循环,和下面的判断条件没关系:[添加链接描述](https://pythontechworld.com/article/detail/o1CrVOKm3yNI)
now_sum = sum-root.val
if now_sum == 0:
self.num += 1
return
else: # 错误2,当已经找到和为0的路径之后还可以继续往下寻找sum为0的路径
sum = now_sum
self.dfs(root.left, sum)
self.dfs(root.right, sum)
def FindPath(self , root: TreeNode, sum: int) -> int:
# write code here
if not root:
return 0
self.dfs(root, sum)
self.FindPath(root.left, sum)
self.FindPath(root.right, sum)
return self.num
所以正确解法应为:
class Solution:
def __init__(self) -> None:
self.num = 0
def dfs(self, root, sum):
if not root:
return
now_sum = sum-root.val
if now_sum == 0:
self.num += 1
self.dfs(root.left, now_sum)
self.dfs(root.right, now_sum)
def FindPath(self , root: TreeNode, sum: int) -> int:
# write code here
if not root:
return 0
self.dfs(root, sum)
self.FindPath(root.left, sum)
self.FindPath(root.right, sum)
return self.num
时间复杂度:O(n^2),其中n为二叉树的结点数,两层dfs嵌套递归
空间复杂度:O(n),每层dfs最深递归栈都只有n
解法2: 1次遍历+哈希表
哈希表中存放到从根节点到该节点的路径和,以及路径和的出现次数。如果路径和减sum存在于哈希表中,说明路径的后半段的路径和为sum。
看看tyy看了解题思路之后默写的:
class Solution:
def __init__(self) -> None:
self.d = {}
self.d[0] = 1
self.res = 0
def little(self, root, sum, all):
if not root:
return
all += root.val # 先统计从根节点到当前节点的路径和
if all - sum in self.d.keys(): # 修改1,应该先判断有无路径,再像哈希表中添加元素
self.res += self.d[all-sum]
if all not in self.d.keys():
self.d[all] = 1
else:
self.d[all] += 1
# if all - sum in self.d.keys():
# self.res += self.d[all-sum]
self.little(root.left, sum, all)
self.little(root.right, sum, all)
return self.res
def FindPath(self, root: TreeNode, sum: int) -> int:
# write code here
if not root:
return self.res
return self.little(root, sum, 0)
时间复杂度:O(n),其中n为二叉树的结点数,遍历一次二叉树,哈希表的操作都是O(1)
空间复杂度:O(n),哈希表大小及递归栈最大为n
JZ86 在二叉树中找到两个节点的最近公共祖先
方法1:路径比较法(深度优先搜索,dfs),官方给出的解法1,具体思路可查看官方解答
step 1:利用dfs求得根节点到两个目标节点的路径:每次选择二叉树的一棵子树往下找,同时路径数组增加这个遍历的节点值。
step 2:一旦遍历到了叶子节点也没有,则回溯到父节点,寻找其他路径,回溯时要去掉数组中刚刚加入的元素。
step 3:然后遍历两条路径数组,依次比较元素值。
step 4:找到两条路径第一个不相同的节点即是最近公共祖先。
这是tyy看了官方解答之后自己写的,修改几次之后终于提交通过:
class Solution:
def __init__(self) -> None:
self.flag = False
def findRoad(self, root, t, o): # t为可变参数,所有每一次递归其值会改变:https://blog.csdn.net/weixin_40875443/article/details/123648262
t.append(root.val) # 修改1:要将节点的值加入
if root.val == o:
self.flag = True
# return # 修改3,如果输入的是根节点,而没有return t,则lowestCommonAncestor函数中的t1没有返回值,就错了呗
return t
if self.flag == False and root.left:
self.findRoad(root.left, t, o)
if self.flag == True:
return t
if self.flag == False and root.right:
self.findRoad(root.right, t, o)
if self.flag == True:
return t
t.pop() # 其实t.pop()是有返回值的,不过这样写也是正确的
return
def lowestCommonAncestor(self , root: TreeNode, o1: int, o2: int) -> int:
# write code here
t1, t2 = [], []
t1 = self.findRoad(root, t1, o1)
# print('t1')
# for j in t1:
# print(j.val)
self.flag = False
t2 = self.findRoad(root, t2, o2)
# print('t2')
# for j in t2:
# print(j.val)
res = None
i = 0
while i <len(t1) and i < len(t2):
if t1[i] == t2[i]:
res = t1[i]
# print(res.val)
i += 1
else:
break
# print('res:', res.val)
return res
时间复杂度:O(n),其中n为二叉树节点数,递归遍历二叉树每一个节点求路径,后续又遍历路径
空间复杂度:O(n),最坏情况二叉树化为链表,深度为n,递归栈深度和路径数组为n
解法2:二叉树递归
参考官方第二种解法的解答,说实话,没大看懂
class Solution:
def lowestCommonAncestor(self , root: TreeNode, o1: int, o2: int) -> int:
# write code here
if not root: # 修改1:注意,和下一个if判断条件的顺序不能颠倒,否则会出错
return -1
if root.val == o1 or root.val == o2:
return root.val
left = self.lowestCommonAncestor(root.left, o1, o2) # 修改2,注意参数的个数,应该输入3个参数,不要写错
right = self.lowestCommonAncestor(root.right, o1, o2)
if left == -1:
return right
if right == -1:
return left
return root.val
时间复杂度:O(n),其中n为节点数,递归遍历二叉树每一个节点
空间复杂度:O(n),最坏情况二叉树化为链表,递归栈深度为n
JZ68 二叉搜索树的最近公共祖先
解法1:二叉树递归
利用左子树比根节点的值小,右子树比根节点的值大的性质,tyy自己想的(思路错了,但是代码有小错误,看了答案之后改对了)
class Solution:
def lowestCommonAncestor(self , root: TreeNode, p: int, q: int) -> int:
# write code here
if not root:
return -1
if (p <= root.val <= q) or (q <= root.val <= p): # 修改1,注意在所有判断条件中不要忘了等于号
return root.val
elif (p <= root.val) and (q <= root.val):
return self.lowestCommonAncestor(root.left, p, q)
elif (p >= root.val) and (q >= root.val):
return self.lowestCommonAncestor(root.right, p, q)
注:用JZ86的解法2的代码也能通过
解法2:搜索路径比较
分别找出两个节点的搜索路径,然后依次比较
tyy看了官方思路之后自己写的代码:
class Solution:
def lowestCommonAncestor(self , root: TreeNode, p: int, q: int) -> int:
# write code here
node1 = root
path1 = []
while node1:
path1.append(node1.val)
if p == node1.val:
break
elif p < node1.val:
node1 = node1.left
continue
elif p > node1.val:
node1 = node1.right
continue
node2 = root # 注:其实可以把这两处代码写成一个函数,分别对p和q调用
path2 = []
while node2:
path2.append(node2.val)
if q == node2.val: # 修改1,不要把q写成p
break
elif q < node2.val:
node2 = node2.left # 修改2,不要把node2写成root
continue
elif q > node2.val:
node2 = node2.right
continue
i = 0
while i < len(path1) and i < len(path2):
if path1[i] == path2[i]:
i += 1
else:
break
return path1[i-1]