- python实现二叉树的遍历以及基本操作
104. 二叉树的最大深度
111. 二叉树的最小深度
236. 二叉树的最近公共祖先 ***
235. 二叉搜索树的最近公共祖先
102. 二叉树的层次遍历
103. 二叉树的锯齿形层次遍历 ***
543. 二叉树的直径 ***
100. 相同的树
101. 对称二叉树
617. 合并二叉树
108. 将有序数组转换为二叉搜索树
109. 有序链表转换二叉搜索树 ***
110. 平衡二叉树 ***
226. 翻转二叉树
404.左叶子之和
257.二叉树的所有路径
112. 路径总和
437.路径总和 III ***
124.二叉树中的最大路径 ***
297. 二叉树的序列化与反序列化
98.验证二叉搜索树
572.另一个树的子树
1367.二叉树中的列表
199. 二叉树的右视图
104. 二叉树的最大深度
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def maxDepth(self, root: TreeNode) -> int:
'''
时间:O(n), 因为每个节点都需要被访问一次
空间复杂度:递归需要使用调用栈
- 最坏: 树完全不平衡,每个节点都只有右节点此时需要递归n次, O(n)
- 最好: 树完全平衡即树额高度是log(n), O(log(n))
'''
if not root:
return 0
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
111. 二叉树的最小深度
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def minDepth(self, root: TreeNode) -> int:
'''
和求最大深度不同:
最小深度 还需要单独考虑当root 没有左子树或右子树 的两种情况
(因为这时直接套用递归公式1 + min(self.minDepth(root.left), self.minDepth(root.right)))
会导致结果总为 1+0,不是正确的最小深度
eg 树为[1,2]时: minDepth应该为2而不是1
'''
if not root:
return 0
elif not root.left:
return 1+self.minDepth(root.right)
elif not root.right:
return 1+self.minDepth(root.left)
else:
return 1 + min(self.minDepth(root.left), self.minDepth(root.right))
236. 二叉树的最近公共祖先
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
#这个解法所有节点只会遍历一次。
if not root or root.val==p.val or root.val==q.val: #如果root为空(返回root=空) 或者p或q结点的值有一个和root的值相等那么最近公共祖先肯定是root
return root
left = self.lowestCommonAncestor(root.left, p, q) #在root的左子树中看是否可以找到p q的最近公共祖先LCA并返回LCA
right = self.lowestCommonAncestor(root.right, p, q)
if not left: #因为p、q 为不同节点且均存在于给定的二叉搜索树中。所以若在左子树中没有找到(p和q均不在左子树中),那么右子树中找到的结果肯定就是p q的LCA
return right
if not right: #同理 若在右子树中没有找到,那么左子树中找到的结果肯定就是p q的LCA
return left
return root #最后的一种情况就是:left和right均不为空,这样一来肯定左右子树都是通过条件root.val==p.val or root.val==q.va跳出递归的,
#也就是说: 对于 lowestCommonAncestor 这个函数的理解的话,它不一定可以返回最近的共同祖先,
# 如果子树中只能找到 p 节点或者 q 节点,它最终返回其实就是 p 节点或者 q 节点。
# 这其实对应于最后一种情况,也就是 left 和 right 都不为 null,说明 p 节点和 q 节点分处于两个子树中,直接 return root
235. 二叉搜索树的最近公共祖先
- 二叉搜索树(二叉排序树)的中序遍历是 递增序列
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# 236的解法可直接用于该题,
# 但因为该题是二叉搜索树(有序),所以通过和根节点的值进行比较就能确定p,q是在左子树还是右子树(p、q 为不同节点且均存在于给定的二叉搜索树中)
if not root or root.val==p.val or root.val==q.val:
return root
if p.val < root.val and q.val < root.val: #p、q均在左子树
return self.lowestCommonAncestor(root.left, p, q)
if p.val > root.val and q.val > root.val: #p、q均在右子树
return self.lowestCommonAncestor(root.right, p, q)
return root #p、q一个在左子树一个在右子树
102. 二叉树的层次遍历
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
### 广度优先遍历BFS: 需要用到辅助队列
if not root:
return []
res=[]
queue=[root] #记住辅助队列中存放TreeNode
while queue:
temp=[] #每次存放树的一层
#保证每次while循环里queue里只有树中的一层元素
for _ in range(len(queue)):
r=queue.pop(0)
temp.append(r.val)#在弹出的时候加入该层临时temp中
if r.left: #不为空的时候加入队列
queue.append(r.left)
if r.right:
queue.append(r.right)
res.append(temp)#将该层加入到最终结果res中
return res
### DFS递归实现
if not root:
return []
res = []
def dfs(index,r):#每次递归的时候都需要带一个index(表示当前的层数),如果当前行对应的list不存在,就加入一个空list进去。
# 假设res是[ [1],[2,3] ], index是3,就再插入一个空list放到res中
if len(res)
103. 二叉树的锯齿形层次遍历
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
queue=[root]
res=[]
index=1 #树的行标号
while queue:
temp=[]
for _ in range(len(queue)):
r = queue.pop(0)
temp.append(r.val)
if r.left:
queue.append(r.left)
if r.right:
queue.append(r.right)
if index%2==0: #在102层次遍历中只是改变: 偶数行需要翻转后再加入res中
temp.reverse()
#list.reverse()仅是list的一个内置翻转的方法,直接改变了原有的list, 返回值是一个None,其作用的结果需要通过打印被作用的列表才可以查看出具体的效果(在字典,字符串或者元组中,是没有这个内置方法的)
#reversed(list)是python自带的一个方法,准确说是一个类,返回反向迭代器,在经过reversed()的作用之后,返回的是一个把序列值经过反转之后的迭代器,所以需要通过遍历,或者List,或者next()等方法,获取作用后的值 ( list(reversed(List)) , tuple(reversed(Tuple), ''.join(reversed(str)))
res.append(temp)
index+=1
return res
543. 二叉树的直径
错误解答: 最大直径是左子树和右子树的最大深度之和,但是万一最大直径没有经过根节点呢?所以说对于树中的每一个结点,都要把它视为根节点,然后比较所有结点的左子树和右子树的最大深度之和,取其中的最大值。
- 二叉树上的任一“路径”上一定有一个结点是所有其他结点的祖先结点(因为“路径”是由一个个父子关系连接而成的),即对于任一结点,以此结点为根的diameter就可以表示为左子树高度 + 右子树高度,而二叉树的diameter就是所有结点为根的diameter中最大的那个
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution: #时间复杂度:O(n), 空间复杂度:O(n)
def __init__(self):
self.maxDiameter=0 #全局变量 , 树中所有结点(作为”路径根结点“)的最大直径即为树的直径
def diameterOfBinaryTree(self, root: TreeNode) -> int:
def traceDepth(r): #辅助函数, 在计算每个r子树最大深度的同时计算其”直径“并更新最大”直径“ , 比104题只多了一步”更新操作“
if not r:
return 0
leftDepth=traceDepth(r.left) #这样先用变量保存防止后面两步中的重复计算
rightDepth=traceDepth(r.right)
self.maxDiameter=max(self.maxDiameter, leftDepth+rightDepth) ###更新最大直径, leftDepth + rightDepth = 以 r 为”路径根结点“时的直径(左子树最大深度 + 右子树最大深度)
return 1 + max(leftDepth, rightDepth) # 求最大深度的递归公式
traceDepth(root) #从root开始遍历整个树这样每个节点都会遍历到, 过程中self.maxDiameter会一直更新为最大的值
return self.maxDiameter
100. 相同的树
- 如果两棵树都是空树,返回True,
- 如果两棵树都不是空,那么分别判断两棵树的根节点的数值,左结点和右结点是否相同
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if not p and not q:
return True
if p and q:
return p.val==q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
return False #剩下左或右子树只有一个为空的情况均是不相等的二叉树
101. 对称二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
### 递归方法:
'''
时间复杂度:O(n),因为遍历了整个输入树一次,所以总的运行时间为 O(n),其中n是树中结点的总数。
空间复杂度:递归调用的次数受树的高度限制。在最糟糕情况下,树是线性的,其高度为 O(n), 此时由栈上的递归调用造成的空间复杂度为 O(n)
'''
if not root:
return True
def isTwoSymmetric(p, q): #判断两个子树是否对称
if not p and not q:
return True
if p and q:
return p.val==q.val and isTwoSymmetric(p.left, q.right) and isTwoSymmetric(p.right, q.left)
return False
return isTwoSymmetric(root.left, root.right)
### 迭代方法:
if not root:
return True
queue=[root.left, root.right]
while queue:
l=queue.pop(0)
r=queue.pop(0) #每次从队列中取出两个节点进行比较
if not(l or r): #德摩根定律:not l and not r, 即两个节点都为空时,必须是continue因为队列里可能剩有其他偶数个节点
continue
if not(l and r) or l.val != r.val: #not(l and r)必须在前,这是说明两个节点只有一个为空时就肯定不对称,后面l.val != r.val也就不用判断而且也不能判断(空节点没有val)
return False
queue.extend([l.left, r.right, l.right, r.left]) #按照镜像对称的方式加入下一波子节点
return True
617. 合并二叉树
class Solution:
def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode:
'''
同步遍历,对于相同位置上的一对结点n1和n2,处理策略是:
若n1和n2都存在,则只需要保留其中一个结点(如n1),将另一结点的值加到此结点上即可(如n1.val += n2.val)。
若n1或n2任一不存在,则合并后的二叉树对应位置上的结点就是存在的那个了。
若n1和n2都不存在,则合并后仍不存在
时间复杂度:O(N),其中 NN 是两棵树中节点个数的较小值。
空间复杂度:O(N),在最坏情况下,会递归 N 层,需要 O(N) 的栈空间。
'''
if t1 and t2: #都存在的话每次保留t1的节点
t1.val += t2.val
t1.left = self.mergeTrees(t1.left, t2.left)
t1.right = self.mergeTrees(t1.right, t2.right)
return t1
return t1 if t1 else t2
108. 将有序数组转换为二叉搜索树
二叉搜索树(Binary Search Tree)是指一棵空树或具有如下性质的二叉树:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值
- 任意节点的左、右子树也分别为二叉搜索树
- 没有键值相等的节点
基于以上性质,我们可以得出一个二叉搜索树的特性:二叉搜索树的中序遍历结果为递增序列。
如何构造树
构造一棵树的过程可以拆分成无数个这样的子问题:构造树的每个节点以及节点之间的关系。对于每个节点来说,都需要:
- 选取节点
- 构造该节点的左子树
- 构造该节点的右子树
因题目要求构造一棵「高度平衡」的树,所以我们在选取节点时选择数组的中点作为根节点,以此来保证平衡性。
- 中序+后序、中序+先序才可以唯一确定一棵二叉树,先序+后序遍历不能唯一确定
- 高度平衡: 意味着每次必须选择升序数组中的中间数字作为根节点。这对于奇数个数的数组选择唯一;但对于偶数个数的数组,要么选择中间位置左边的元素作为根节点,要么选择中间位置右边的元素作为根节点,不同的选择方案会创建不同的平衡二叉搜索树。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode: #最后还要返回创建后的树也就是root节点
# #每次选择升序数组的中间元素作为根结点来构造二叉树
# #时间: O(n), 空间: O(N),二叉搜索树空间O(N),递归栈深度O(logN)
if not nums:
return None
mid_index = len(nums) // 2 #得到数组中的”中间元素“的index
root = TreeNode(nums[mid_index]) #选择数组中的中间元素值构造根结点
root.left = self.sortedArrayToBST( nums[:mid_index] ) #递归构造左子树
root.right = self.sortedArrayToBST( nums[mid_index+1:] )
return root#返回根结点(也就是数组的中间元素作为的根结点)
109. 有序链表转换二叉搜索树
与108一样的要求,只是把升序数组换成了升序链表
# Definition for singly-linked list.
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
### 利用 快慢指针 找到链表的中间node再类似108题一样来构造平衡树
# 时间:O(NlogN)
# 空间:O(logN)。使用递归需要考虑递归栈的空间复杂度。对于一棵非平衡二叉树O(N),
# 但问题描述中要求维护一棵平衡二叉树,所以保证树的高度上界为O(logN),因此空间复杂度为O(logN)。
def sortedListToBST(self, head: ListNode) -> TreeNode:
'''
slow_ptr 每次向后移动一个节点而 fast_ptr 每次移动两个节点
当 fast_ptr 到链表的末尾时 slow_ptr 就访问到链表的中间元素
对于一个偶数长度的数组,中间两个元素都可用来作二叉搜索树的根
'''
def findmid(head, tail):
slow = head
fast = head
while fast != tail and fast.next!= tail :
slow = slow.next
fast = fast.next.next
return slow
def helper(head, tail): #递归利用链表构造树
if head == tail:
return None
node = findmid(head, tail)
root = TreeNode(node.val)
root.left = helper(head, node)
root.right = helper(node.next, tail)
return root
return helper(head, None) #只有尾结点的next为None
### 先将链表转换成一个升序数组后再进行108中的操作,拿空间换时间
# 时间:O(N)
# 空间:O(N)。使用递归需要考虑递归栈的空间复杂度。对于一棵非平衡二叉树O(N),
# 但问题描述中要求维护一棵平衡二叉树,所以保证树的高度上界为O(logN),因此空间复杂度为O(logN)。
def sortedListToBST(self, head: ListNode) -> TreeNode:
def mapListToValues(head): #将链表转换成数组
vals = []
while head:
vals.append(head.val)
head = head.next
return vals
nums=mapListToValues(head)
def sortedArrayToBST(nums):
# #每次选择升序数组的中间元素作为根结点来构造二叉树
# #时间: O(n), 空间: O(N),二叉搜索树空间O(N),递归栈深度O(logN)
if not nums:
return None
mid_index = len(nums) // 2
root = TreeNode(nums[mid_index])
root.left = sortedArrayToBST( nums[:mid_index] )
root.right = sortedArrayToBST( nums[mid_index+1:] )
return root#返回根结点
return sortedArrayToBST(nums)
110. 平衡二叉树
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
# 自顶向下的递归: 利用104题中判断二叉树最大深度的函数,左子树和右子树的深度差小于等于1即为平衡二叉树
# 时间:O(NlogN), 空间:O(N),如果树完全倾斜,递归栈可能包含所有节点。
def depth(root): #计算一个树的(最大)深度/高度
if not root:
return 0
return 1 + max( depth(root.left), depth(root.right) )
if not root:
return True
if abs(depth(root.left) - depth(root.right))>1: #一旦一个子树的左右树高度差绝对值大于1就肯定不是平衡的
return False
return self.isBalanced(root.left) and self.isBalanced(root.right) #继续递归判断下一波左右子节点
def isBalanced(self, root: TreeNode) -> bool:
# 自底向上的递归: 上面的方法计算depth存在大量冗余。
# 自底向上计算,每个子树的高度只会计算一次。可以递归先计算当前节点的子节点高度,然后再通过子节点高度判断当前节点是否平衡,从而消除冗余。
# 使用与方法一中定义的depth()方法。自底向上与自顶向下的逻辑相反,首先判断子树是否平衡,然后比较子树高度判断父节点是否平衡
# 时间:O(N),计算每棵子树的高度和判断平衡操作都在恒定时间内完成。 空间:O(N)
def helper(root): # 计算root树高度的同时判断子树的高度差是否大于1,一旦发现大于1就提前阻断
if not root:
return 0
left = helper(root.left)
right = helper(root.right)
if left==-1 or right==-1: # 判断左右子树是否有一个不平衡
return -1 #一旦进入这个分支就表示该树不平衡
return max(left, right)+1 if abs(right - left)<=1 else -1
#计算深度或直接返回不平衡的结果-1,这样一旦发现不平衡后函数就会一直返回-1不用再计算深度达到提前阻断的效果(树的高度不可能是-1,所以若helper返回-1就表示已经不平衡了)
return helper(root) != -1 #helper(root)返回-1(不平衡)时整个函数就返回 False
### ”这个方法并没有提前阻断“
# self.res = True # 这样在子函数中不用声明nonlocal就可以改变res的值
# def helper(root): # 计算root树高度的同时判断子树的高度差是否大于1,一旦发现大于1就提前阻断
# if not root:
# return 0
# left = helper(root.left)
# right = helper(root.right)
# if abs(right - left) > 1: # 判断:一旦进入这个分支就表示该树不平衡
# self.res = False
# return max(left, right) + 1 # 计算深度
# helper(root)
# return self.res
226. 翻转二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
### 递归: 直接从字面上,当root非空时利用python交换左右节点即可
# 时间:O(N)
# 空间:O(N)
if root:
root.left, root.right = self.invertTree(root.right), self.invertTree(root.left)
return root
### 迭代: 利用DFS,stack
if not root:
return None
stack=[root] #或者使用队列queue, 后面pop()改成pop(0)就好了
while stack:
node=stack.pop()
node.left, node.right = node.right, node.left
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return root
404.左叶子之和
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def sumOfLeftLeaves(self, root: TreeNode) -> int:
left_sum=0 #会累加
if not root:
return 0
if root.left and not root.left.left and not root.left.right: #围绕root.left, 保证其一定是左叶子节点
left_sum+=root.left.val
return left_sum + self.sumOfLeftLeaves(root.left)+self.sumOfLeftLeaves(root.right)
257.二叉树的所有路径
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def binaryTreePaths(self, root: TreeNode) -> List[str]:
### 迭代DFS来实现: 时间空间复杂度均为O(N)
if not root:
return []
paths=[]
stack=[(root, str(root.val))] #栈内存放(节点, 到该点为止的路径)
while stack:
node, path = stack.pop()
if not node.left and not node.right: #到达叶子节点的时候, 就可以将该条路径加入到paths中
paths.append(path)
if node.right:
stack.append( (node.right, path + '->' + str(node.right.val)) )
if node.left:
stack.append( (node.left, path + '->' + str(node.left.val)) )
return paths
### 递归
def construct_paths(root, path): #递归获取的路径的同时在paths中加入路径
if root:
path += str(root.val)
if not root.left and not root.right: # 当前节点是叶子节点
paths.append(path) #把路径加入到paths中,外层函数中的string变量paths是可变类型不用再声明nonlocal就可以直接改变其值
else: # 当前节点不是叶子节点,继续递归遍历
path += '->'
construct_paths(root.left, path)
construct_paths(root.right, path)
paths = []
construct_paths(root, '')
return paths
112. 路径总和
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
# 普通递归: 注意首先要明确 问题终止条件, 然后在分别考虑左右子树上同样的操作或判断
'''
时间:O(N), 访问每个节点一次
空间:最坏情况下,整棵树是非平衡的,例如每个节点都只有一个孩子,递归会调用N次(树的高度,因此栈的空间开销是 O(N)
最好情况下,树是完全平衡的,高度只有log(N),因此在这种情况下空间复杂度只有 O(log(N))
'''
if not root:
return False
if not root.left and not root.right and root.val==sum: #当前的root节点无左右节点且root的值与当前的sum相等,说明已到达叶子节点且已找到一条路径
return True
return self.hasPathSum(root.left, sum-root.val) or self.hasPathSum(root.right, sum-root.val) #继续在左或右子树中找和为sum-root.val的路径
# 利用深度优先策略访问每个节点,同时更新剩余的目标和。
# 用栈将递归转成迭代的形式。深度优先搜索在除了最坏情况下都比广度优先搜索更快。
# 最坏情况是指满足目标和的 root->leaf 路径是最后被考虑的,这种情况下深度优先搜索和广度优先搜索代价是相通的
'''
时间:O(N), 访问每个节点一次
空间:最坏情况下 O(N)
最好情况下 O(log(N))
'''
if not root:
return False
stack = [(root, sum), ] #从包含根节点的栈开始模拟,开始目标为sum
while stack:
node, curr_sum = stack.pop()
if not node.left and not node.right and node.val == curr_sum: #与上面的递归类似的判断
return True
#继续DFS遍历并判断左右子树,非空则压入栈中留着接下来判断,
if node.right:
stack.append((node.right, curr_sum - node.val))
if node.left:
stack.append((node.left, curr_sum - node.val)) #先进后出所以每次都是先判断左子树
return False #遍历完整个stack/树都没有进入True的那个分支就说明没有找到
437. 路径总和 III
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> int:
'''
双重递归,效率非常低,存在大量重复计算
'''
if not root:
return 0
# 返回 从root点出发的路径个数pathSumFrom + root的左子树上所有的路径个数pathSum + root的右子树上所有的路径个数pathSum
return self.pathSumFrom(root, sum) + self.pathSum(root.left, sum) + self.pathSum(root.right, sum)
def pathSumFrom(self, node, sum): #(从该点出发)以每个节点为根节点,算一遍路径和为sum的有几条
if not node:
return 0
return (1 if node.val == sum else 0) + self.pathSumFrom(node.left, sum - node.val) + self.pathSumFrom(node.right, sum - node.val)
### 时间:O(N), 注意题中:路径方向必须是向下的(必须从父节点到子节点)
def pathSum(self, root: TreeNode, sum: int) -> int:
'''
优化: 类似560,利用前缀和,hashtable 进行优化
'''
prefixSumTree = {0:1} #key是前缀和, value是大小为key的前缀和出现的次数
self.count = 0 #满足条件的路径计数, 加上self. 可以在内层函数dfs()中直接改变其值 不用再声明nonlocal
prefixSum = 0 #从 根结点 到当前结点的前缀和(路径上的结点之和)
self.dfs(root, sum, prefixSum, prefixSumTree)
return self.count
def dfs(self, root, sum, prefixSum, prefixSumTree):
if not root:
return 0
prefixSum += root.val #加上当前结点值的前缀和
#查找在 <前缀和, 该前缀和出现次数> 的hashmap上,有没有前缀和为 prefixSum-sum 的节点,
#存在,即表示有路径之和为sum的路径存在,count加上key=prefixSum-sum的value。然后递归进入左右子树。
oldSum = prefixSum - sum
if oldSum in prefixSumTree:# 看是否在哈希表中存在
self.count += prefixSumTree[oldSum]
# 将当前的前缀和存入哈希表中,不存在:初始化(0+1),存在:原来的次数+1
prefixSumTree[prefixSum] = prefixSumTree.get(prefixSum, 0) + 1
self.dfs(root.left, sum, prefixSum, prefixSumTree)
self.dfs(root.right, sum, prefixSum, prefixSumTree)
'''前缀和对于当前路径来说是唯一的,当前记录的前缀和,在回溯结束,回到本层时去除,保证其不影响其他分支的结果
左右子树遍历完成之后,回到当前层,需要把当前节点添加的前缀和的一次 次数 去掉。避免回溯之后影响上一层。
因为思想是前缀和,不属于前缀的,我们就要去掉它。'''
prefixSumTree[prefixSum] -= 1 ### 千万不能忘
124. 二叉树中的最大路径
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def maxPathSum(self, root: TreeNode) -> int:
'''
时间:O(N)
空间: O(log(N))。我们需要一个大小与树的高度相等的栈开销,对于二叉树空间开销是O(log(N)
- 先递归计算出”单边的最大值“(包含该节点,若 left, right 小于 0 则不包含节点下该分支)
- 该节点下最大值是 left + right + val (若 left, right 小于 0 则不包含节点下该分支),比较所有节点下的最大值 所求即为 二叉树中的最大路径和
'''
def maxPathGain(node):
# 返回的是以node为 ”路径起始节点“(非中间连接节点) 的最大收益(只能沿着左或右走,要么极端情况就是不选左也不选右(为负时设为0)该路径只有node.val)
if not node: #无关给定的二叉树root非不非空,这只是递归函数的出口,几乎所有二叉树的递归函数都要有 ”not node“ 的判断
return 0
left_gain = max(maxPathGain(node.left), 0) #不为负:如果是负收益就 不选中 该左子树,即 left_gain设为0
right_gain = max(maxPathGain(node.right), 0)
self.max_sum = max(self.max_sum, node.val + left_gain + right_gain) #更新max_sum,
# node.val + left_gain + right_gain :其可以表示 node为中间连接节点或为起始节点(往左或往右或只有node.val)的多种情况,
# 是因为前面left/right_gain计算时使用了max(x,0), 可表示选或不选
return node.val + max(left_gain, right_gain) #以node为起始点, 往最大收益的那边走(单边最大值)
self.max_sum = float('-inf') #因为结点可能为负值,所以最大值要初始化为负无穷
maxPathGain(root)
return self.max_sum
297. 二叉树的序列化与反序列化
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
### 前序遍历 实现二叉树序列化和反序列化
if not root:
return 'null'
return str(root.val) + ',' + self.serialize(root.left) + ',' + self.serialize(root.right)
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
def helper(arr):
if not arr:
return None
root_val = arr.pop(0) #得到根结点的val, 同时arr中还要去掉根val, 因为后面还要递归遍历
if root_val == 'null':
return None
else:
root = TreeNode(root_val)
root.left = helper(arr)
root.right = helper(arr)
return root
return helper(data.split(','))
def serialize(self, root):
"""Encodes a tree to a single string
:type root: TreeNode
:rtype: str
"""
### 层序遍历 实现二叉树序列化和反序列化
if not root:
return ''
queue = [root]
s = ''
while queue:
node = queue.pop(0)
if not node:
s += 'null,'
continue
s += str(node.val) + ','
queue.append(node.left)
queue.append(node.right)
return s
def deserialize(self, s):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if not s:
return None
ls = s.split(',')[:-1] #最后一个结点后面的逗号要去掉
def helper(idx):
if idx >= len(ls):
return None
root = TreeNode(ls[idx])
root.left = helper(2*idx+1)
root.right = helper(2*idx+2)
return root
return helper(0)
# Your Codec object will be instantiated and called as such:
# codec = Codec()
# codec.deserialize(codec.serialize(root))