968 监控二叉树
最小支配集问题
叶子结点位于树的边界,不会安装摄像头
有孩子结点是叶结点的结点必须安摄像头
# 图论里面的最小支配集问题,只有在二叉树这种特殊的图下 才能够求解,两种方法(贪心or动态规划)
class Solution(object):
def minCameraCover(self, root):
def solve(node):
if not node:
return 0, 0, float('inf')
L = solve(node.left)
R = solve(node.right)
'''
条件0:当前节点的子节点都被覆盖,但不包括当前节点
条件1:当前节点及所有子节点都被覆盖,但是当前节点位置无摄像头
条件2:当前节点位置有摄像头
解释一下 dp数组
dp0代表当前节点未被覆盖,但该节点的子节点都被覆盖 时所需要的最小摄像头的数量
dp1代表当前节点和当前节点的子节点都被覆盖,但当前节点没有摄像头,则子节点中至少有一个位于状态2,所以可以把当前的点也覆盖了 时所需要的最小摄像头的数量
dp2代表当前节点有摄像头,则子节点可以处于任意状态 需要的最小摄像头的数量
'''
dp0 = L[1] + R[1] # 若想让此节点不被覆盖 但是子节点都被覆盖,则左右两个节点都要满足条件1,此节点等于左右节点dp1之和
dp1 = min(L[2] + min(R[1:]), R[2] + min(L[1:])) # 若想让当前节点满足条件1 有两种情况:1.左节点覆盖住了此节点,此时右节点要想满足全覆盖也有两种情况(dp1 and dp2,取最小值即可)2. 右节点覆盖住了此节点,此时左节点要想满足全覆盖也有两种情况(dp1 and dp2,取最小值即可)
dp2 = 1 + min(L) + min(R) # 若想让当前节点满足条件2,此时左右节点都被此节点覆盖,所以左右节点可以选取任意一种状态
# print(dp0, dp1, dp2)
return dp0, dp1, dp2
return min(solve(root)[1:])
好理解
后序遍历 从下往上标记状态
class Solution:
def minCameraCover(self, root: TreeNode) -> int:
#0: 该点和子树均被覆盖,且该节点安装了监控器
#1:该结点周围有监控器(即该节点和该节点的子节点都被监控到)或该结点不存在,但该点无监控器,都不用安装监控器
#2:该结点附近没有监控器,该节点未被监控到但是所有子节点都被监控到,表示其附近需要监控器
#从下往上标记节点状态
self.ans=0
def dfs(node):
if not node:
return 1
l=dfs(node.left)
r=dfs(node.right)
#子结点只要有一个未被监控,则该节点放监控
if l==2 or r==2:
self.ans+=1
return 0
#孩子节点只要有一个放了监控,该节点就可以被监控到
if l==0 or r==0:
return 1
return 2
if not root:
return 0
#若根节点未被监控,需要放监控器
if dfs(root)==2:
self.ans+=1
return self.ans
979 在二叉树中分配金币:
dfs(node):为该节点的金币过载量
对于一个叶子节点,需要移动到它中或需要从它移动到它的父亲中的金币数量为 过载量 = Math.abs(num_coins - 1)
我们可以计算出这个节点与它的子节点之间需要移动金币的数量为 abs(dfs(node.left)) + abs(dfs(node.right))(子节点移动给他或者他分配给子节点)
这个节点金币的过载量为 node.val + dfs(node.left) + dfs(node.right) - 1。
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def distributeCoins(self, root: TreeNode) -> int:
self.ans=0
def dfs(node):
if not node:
return 0
l=dfs(node.left)
r=dfs(node.right)
#移动的次数等于左右子树过载量绝对值的和
self.ans+=abs(l)+abs(r)
#每个节点的过载量
return node.val+l+r-1
dfs(root)
return self.ans
1104 二叉树寻路
完美二叉树
如果是正常从左到右,从上到下构造,则下一层的节点是当前层的2倍。
数学解法
class Solution:
def pathInZigZagTree(self, label: int) -> List[int]:
if label==0:
return []
if label==1:
return [1]
res=[label]
while label>1:
#当前层
level=int(math.log2(label))
#当前层起始数字
level_start=2**level
#上一行用于连接label的数字与上一行起始数字的偏移量
remain=(label-level_start)//2
# 迭代label,上一层对应路径的节点
label=level_start-remain-1
res.append(label)
return res[::-1]
1315. 祖父节点值为偶数的节点和
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def sumEvenGrandparent(self, root: TreeNode) -> int:
if not root:
return 0
self.ans=0
def dfs(grandparent,parent,node):
if not node:
return 0
if grandparent.val%2==0:
self.ans+=node.val
dfs(parent,node,node.left)
dfs(parent,node,node.right)
if root.left:
dfs(root,root.left,root.left.left)
dfs(root,root.left,root.left.right)
if root.right:
dfs(root,root.right,root.right.left)
dfs(root,root.right,root.right.right)
return self.ans
在搜索状态三元组 (grandparent, parent, node) 中,grandparent 和 parent 这两项我们只使用了它的值,而不使用节点本身,因此我们可以在搜索状态中用值来替换这些节点。
我们可以假设根节点有一个虚拟的祖父节点和父节点,它们的值都为 1。在搜索时,我们使用三元组 (gp_val, p_val, node) 表示搜索状态,其中 gp_val 和 p_val 分别表示祖父节点和父节点的值,node 表示当前节点。这样以来,我们就可以直接从状态 (1, 1, root) 开始直接对根节点进行搜索了。
class Solution:
def sumEvenGrandparent(self, root: TreeNode) -> int:
if not root:
return 0
self.ans=0
def dfs(grandparent_val,parent_val,node):
if not node:
return 0
if grandparent_val%2==0:
self.ans+=node.val
dfs(parent_val,node.val,node.left)
dfs(parent_val,node.val,node.right)
dfs(1,1,root)
return self.ans
广度优先搜索:
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def sumEvenGrandparent(self, root: TreeNode) -> int:
if not root:
return 0
ans=0
stack=deque()
stack.append(root)
while stack:
node=stack.popleft()
if node.val%2==0:
if node.left:
if node.left.left:
ans+=node.left.left.val
if node.left.right:
ans+=node.left.right.val
if node.right:
if node.right.left:
ans+=node.right.left.val
if node.right.right:
ans+=node.right.right.val
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
return ans
332 重新安排行程
欧拉路径
每次对当前出发点的“剩余可用目的地”循环,优先去名字值小的
结束条件是层数达到完全遍历,若未达到又无可用目的地,说明这条路走不通了,进行回溯
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
queue=defaultdict(list)
for i,j in tickets:
queue[i].append(j)
for i in queue:
#邻接表排序
queue[i].sort()
self.ans=[]
def dfs(v):
while queue[v]:
#路径检索
dfs(queue[v].pop(0))
#某个起点不存在路径时就会跳出while 循环,进行回溯
#相对先找不到路径的一定是放在相对后面
#存到最前面,最先存进来的是最后找不到路径的
self.ans.insert(0,v)
dfs('JFK')
return self.ans
1302 层数最深叶子节点的和
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def deepestLeavesSum(self, root: TreeNode) -> int:
if not root:
return 0
res=defaultdict(list)
stack=[(root,1)]
level=0
while stack:
level+=1
for _ in range(len(stack)):
node,step=stack.pop(0)
if not node.left and not node.right:
res[step].append(node.val)
if node.left:
stack.append((node.left,step+1))
if node.right:
stack.append((node.right,step+1))
return sum(res[level])
98 验证二叉搜索树
二叉搜索树中序遍历是递增数列:
栈最多存储 n 个节点,因此需要额外的 O(n) 的空间
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
if not root:
return True
stack=[]
pre=float('-inf')
node=root
while stack or node:
while node:
stack.append(node)
node=node.left
node=stack.pop()
# 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
if node.val<=pre:
return False
pre=node.val
node=node.right
return True
递归:
函数表示考虑以 root 为根的子树,判断子树中所有节点的值是否都在 (l,r)的范围内(注意是开区间)。如果 root 节点的值 val 不在 (l,r)的范围内说明不满足条件直接返回,否则我们要继续递归调用检查它的左右子树是否满足,如果都满足才说明这是一棵二叉搜索树。
那么根据二叉搜索树的性质,在递归调用左子树时,我们需要把上界 upper 改为 root.val,即调用 helper(root.left, lower, root.val),因为左子树里所有节点的值均小于它的根节点的值。同理递归调用右子树时,我们需要把下界 lower 改为 root.val,即调用 helper(root.right, root.val, upper)
递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,即二叉树的高度
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
if not root:
return True
def dfs(node,left,right):
if not node:
return True
if left<node.val<right:
return dfs(node.left,left,node.val) and dfs(node.right,node.val,right)
return False
return dfs(root,float('-inf'),float('inf'))
501 二叉搜索树中的众数
最简单方法 中序遍历得到二叉搜索树对应的数组,将每个值的频率存到字典中
class Solution:
def findMode(self, root: TreeNode) -> List[int]:
if not root:
return []
self.res=defaultdict(int)
def dfs(node):
if not node:
return
dfs(node.left)
self.res[node.val]+=1
dfs(node.right)
dfs(root)
res=sorted(self.res.items(),key=lambda x:x[1],reverse=True)
maxium=res[0][1]
ans=[]
for i,v in res:
if v==maxium:
ans.append(i)
return ans
不使用额外空间:利用中序遍历,判断与前一个值是否相等,并且记录最大频率
class Solution:
def findMode(self, root: TreeNode) -> List[int]:
if not root:
return []
self.cur=0
self.pre=float('inf')
self.maxnum=0
self.res=[]
def dfs(node):
if not node:
return
dfs(node.left)
if node.val==self.pre:
self.cur+=1
else:
self.cur=1
if self.cur==self.maxnum:
self.res.append(node.val)
if self.cur>self.maxnum:
self.res=[node.val]
self.maxnum=self.cur
self.pre=node.val
dfs(node.right)
dfs(root)
return self.res
99 恢复二叉搜索树
时间空间复杂度都是O(n)
这里被交换了两个节点,因此中序遍历是一个几乎排好序的数组,其中有两个元素被交换。识别排序数组中两个交换元素是可以在线性时间内解决的经典问题。
def find_two_swapped(nums: List[int]) -> (int, int):
n = len(nums)
x = y = -1
for i in range(n - 1):
if nums[i + 1] < nums[i]:
y = nums[i + 1]
# first swap occurence
if x == -1:
x = nums[i]
# second swap occurence
else:
break
return x, y
中序遍历递归:找到需要被交换的两个节点
若要找到交换的节点,就记录中序遍历中的最后一个节点 pred(即当前节点的前置节点),并与当前节点的值进行比较。如果当前节点的值小于前置节点 pred 的值,说明该节点是交换节点之一。
交换的节点只有两个,因此在确定了第二个交换节点以后,可以终止遍历。
中序遍历顺序是 4,2,3,1,我们只要找到节点4和节点1交换顺序即可!
这里我们有个规律发现这两个节点:
第一个节点,是第一个按照中序遍历时候前一个节点大于后一个节点,我们选取前一个节点,这里指节点4;
第二个节点,是在第一个节点找到之后, 后面出现前一个节点大于后一个节点,我们选择后一个节点,这里指节点1;
class Solution:
def recoverTree(self, root: TreeNode) -> None:
"""
Do not return anything, modify root in-place instead.
"""
def dfs(node):
nonlocal x,y,pre
if not node:
return
dfs(node.left)
#因为只有两个节点被错误交换,中序遍历只需要判断是否大于前一个值
if pre and node.val<pre.val:
y=node
if not x:
x=pre
else:
return
pre=node
dfs(node.right)
x=y=pre=None
dfs(root)
x.val,y.val=y.val,x.val
return root
迭代:
class Solution:
def recoverTree(self, root: TreeNode) -> None:
"""
Do not return anything, modify root in-place instead.
"""
if not root:
return
x=y=pre=None
stack=[]
node=root
while stack or node:
while node:
stack.append(node)
node=node.left
node=stack.pop()
if pre and node.val<pre.val:
y=node
if x is None:
x=pre
pre=node
node=node.right
x.val,y.val=y.val,x.val
return root