删除树节点

Submission

运行时间: 52 ms

内存: 18.0 MB

# from collections import defaultdict
# class Solution:
#     def deleteTreeNodes(self, nodes: int, parent: List[int], value: List[int]) -> int:
#         g=defaultdict(list)
#         # n=len(parent)
#         for i,p in enumerate(parent):
#             g[p].append(i)  
#         # ans=list(range(nodes))  

#         def dfs(i):
#             # ret=value[i]
#             # cnt=1
#             st=[(value[i],1)]
#             for j in g[i]:
#                 # cnt+=dfs[j][1]
#                 # ret+=dfs(j)[0]
#                 nxt=dfs[j]
#                 st.append(nxt)
#             ret,cnt=0,0
#             for r,c in st:
#                 ret+=r
#                 cnt+=c
#             if ret==0:
#                 return 0,0
#             return (ret,cnt) 

#         return dfs(0)[1]     
        
# # # my:DFS+图哈希(refer 官解)                     
# from collections import defaultdict
# class Solution:
#     def deleteTreeNodes(self, nodes: int, parent: List[int], value: List[int]) -> int:
#         g=defaultdict(list)
#         # n=len(parent)
#         for i,p in enumerate(parent):
#             g[p].append(i)  
#         # ans=list(range(nodes)) 
#         ans=[1]*nodes 

#         def dfs(i):
#             # ret=value[i]
#             # cnt=1
#             # st=[(value[i],1)]
#             for j in g[i]:
#                 # nxt=dfs[j]
#                 # st.append(nxt)
#                 dfs(j)
#                 value[i]+=value[j]
#                 ans[i]+=ans[j]
            
#             if value[i]==0:
#                 ans[i]=0
#             #     return 0,0
#             # return (ret,cnt) 
#         dfs(0)
#         return ans[0] 

# ### 官1:DFS+哈希
# class Solution:
#     def deleteTreeNodes(self, nodes: int, parent: List[int], value: List[int]) -> int:
#         edges = {x: list() for x in range(nodes)}
#         for x, p in enumerate(parent):
#             if p != -1:
#                 edges[p].append(x)
        
#         node_cnt = [1] * nodes
        
#         def dfs(u):
#             for v in edges[u]:
#                 dfs(v)
#                 value[u] += value[v]
#                 node_cnt[u] += node_cnt[v]
#             if value[u] == 0:
#                 node_cnt[u] = 0

#         dfs(0)
#         return node_cnt[0]


# # # my:DFS+图哈希(后序遍历返回结果)            
# from collections import defaultdict
# class Solution:
#     def deleteTreeNodes(self, nodes: int, parent: List[int], value: List[int]) -> int:
#         g=defaultdict(list)
#         # n=len(parent)
#         for i,p in enumerate(parent):
#             g[p].append(i)  
#         # ans=list(range(nodes)) 
#         # ans=[1]*nodes 

#         def dfs(i):
#             ret=value[i]
#             cnt=1
#             # st=[(value[i],1)]
#             for j in g[i]:
#                 nr,nc=dfs(j) ### 这种写法可以通过
#                 ret+=nr 
#                 cnt+=nc
#                 # dfs(j)
#                 # value[i]+=value[j]
#                 # ans[i]+=ans[j]
            
#             if ret==0:
#                 return 0,0
#             return (ret,cnt) 
#         return dfs(0)[1] 

### 官2:拓扑排序 递归的归过程(就是根据出度为0的叶子节点更新父节点)
from collections import deque
class Solution:
    def deleteTreeNodes(self,nodes,parent,value):
        in_deg=[0]*nodes
        for p in parent:
            if p!=-1:
                in_deg[p]+=1

        node_cnt=[1]*nodes 
        q=deque()
        for i in range(nodes):
            if in_deg[i]==0:
                q.append(i)

        while q:
            v=q.popleft()
            if value[v]==0:
                node_cnt[v]=0
            u=parent[v]
            if u!=-1:
                value[u]+=value[v]
                node_cnt[u]+=node_cnt[v]
                in_deg[u]-=1
                if in_deg[u]==0:
                    q.append(u) 
        
        return node_cnt[0]
                

Explain

本题解采用拓扑排序的方法来解决删除树节点的问题。首先,计算每个节点的入度,然后使用一个队列来存储所有入度为0的节点(即叶子节点)。从这些叶子节点开始,逐步向上更新其父节点的节点值和节点数量。如果某个节点的值更新后为0,则该节点以及其所有子节点都不应被计算在内,因此将其节点数量设置为0。最终,队列中会处理每个节点,直到到达根节点。这种方法从底部叶子节点向上至根节点逐层剪枝,直到所有的节点都被处理过。

时间复杂度: O(n)

空间复杂度: O(n)

# from collections import deque

class Solution:
    def deleteTreeNodes(self, nodes, parent, value):
        in_deg = [0] * nodes  # 存储每个节点的入度
        for p in parent:
            if p != -1:
                in_deg[p] += 1  # 计算入度

        node_cnt = [1] * nodes  # 初始化每个节点的计数为1
        q = deque()
        for i in range(nodes):
            if in_deg[i] == 0:
                q.append(i)  # 将所有叶子节点加入队列

        while q:
            v = q.popleft()
            if value[v] == 0:
                node_cnt[v] = 0  # 如果节点值为0,设置节点计数为0
            u = parent[v]
            if u != -1:
                value[u] += value[v]  # 更新父节点的值
                node_cnt[u] += node_cnt[v]  # 更新父节点的计数
                in_deg[u] -= 1  # 减少父节点的入度
                if in_deg[u] == 0:
                    q.append(u)  # 如果父节点入度为0,加入队列

        return node_cnt[0]  # 返回根节点的节点计数

Explore

拓扑排序是处理具有方向性从属关系的问题的一种有效方法,特别是在处理需要从子节点向父节点聚合信息的场景中。在此题中,从叶子节点开始向根节点聚合节点的值和计数信息,能够确保在处理每个节点时,其所有子节点的信息已经被完全处理和更新,这样可以有效地减少重复计算和回溯,提高效率。相比之下,DFS和BFS虽然也能实现节点的遍历,但在处理节点删除或值更新时,可能需要更复杂的回溯操作或多次遍历。

在题解中,当某个节点的值更新为0后,该节点以及其所有子节点应被视为在最终的节点计数中不再被包含。这是因为题目的目的是计算保留的有效节点的总数,节点值为0意味着这个节点及其子树不应被计入最终的统计结果中。因此,在实现中,节点值为0时会将该节点的节点计数设置为0,并且在更新父节点时,不再将这些节点的计数加到父节点上。

在题解的逻辑中,即使某个父节点的值在更新后变为0,其父节点的值和节点数量仍需要继续更新。这是因为父节点的值变为0是在加入子节点的值之后的结果,而其它子节点的值可能仍然需要加到这个父节点上。只有当处理到这个父节点本身,并发现其值为0时,才将其计数设置为0,并在后续的更新中不再计入其父节点的计数。

在代码实现中,首先识别所有入度为0的节点,即叶子节点,将它们加入队列。这些节点是没有子节点的,因此可以安全地开始处理它们。在处理过程中,每当一个节点被处理(即从队列中移除并更新其父节点的值和计数),其对应的父节点的入度会减少。当父节点的入度减少到0时,意味着该父节点的所有子节点都已处理完毕,此时将父节点加入队列。这个逐层处理保证了从叶子节点向上至根节点的正确更新顺序。