目录
树上节点加锁系统设计 —— LeetCode 1993 题解析与实现
树上节点加锁系统设计 —— LeetCode 1993 题解析与实现
题目描述
给定一棵含有 n 个节点的树,节点编号从 0 到 n-1。树结构用一个父节点数组 parent
表示,其中 parent[i]
是第 i 个节点的父节点。根节点编号为 0,且 parent[0] = -1
。
你需要设计一个数据结构来实现树中节点的 加锁(lock)、解锁(unlock) 和 升级(upgrade) 操作,满足以下规则:
- lock(num, user):给节点
num
上锁,锁定该节点的用户为user
。只有当节点未被锁定时,才能上锁。若成功上锁返回true
,否则返回false
。 - unlock(num, user):解锁节点
num
。只有当该节点当前由用户user
锁定时,才能解锁。若成功解锁返回true
,否则返回false
。 - upgrade(num, user):给节点
num
上锁(用户为user
),并解锁其所有被锁定的子孙节点。仅当满足以下三个条件时才可执行升级:
-
- 节点
num
当前未被锁定。 - 节点
num
至少有一个被锁定的子孙节点(可以是任意用户锁定)。 - 节点
num
的所有祖先节点均未被锁定。
- 节点
满足上述条件时,执行升级操作,返回 true
;否则返回 false
。
题目要求总结
- 设计一个支持上述三种操作的数据结构。
- 对树结构的父节点数组进行初始化。
- 调用
lock
、unlock
、upgrade
函数时需高效地满足条件判断。 - 节点锁定状态和用户信息需要准确维护。
- 操作次数和节点数均有限制,考虑时间复杂度。
解题分析
本题的核心难点在于如何快速判断:
- 一个节点是否有锁定的祖先节点。
- 一个节点的所有子孙中是否存在已锁定的节点。
- 如何快速找到并解锁所有被锁定的子孙节点。
同时,每次操作都要对锁定状态进行更新。
关键点
- 维护锁定状态:用一个数组记录每个节点是否被锁定,以及锁定的用户ID。
- 快速判断祖先锁状态:
-
- 由于父节点信息给出,可以沿着父节点路径向上遍历检查祖先节点是否被锁。
- 该操作在最坏情况下是树高的时间复杂度,但考虑树最大2000节点,操作最多2000次,可接受。
- 快速查找锁定的子孙:
-
- 通过初始化时,将每个节点的子节点保存下来,构造子节点列表。
- 使用DFS递归遍历子树,找到所有已锁的子孙。
- 同样,考虑节点数和操作限制,DFS遍历子孙节点是可行的。
- 解锁子孙并锁定当前节点(升级):
-
- 在满足条件后,遍历所有锁定的子孙节点进行解锁。
- 锁定当前节点。
解题方法
数据结构设计
- parent数组:直接存储父节点信息。
- children数组:初始化时从 parent 数组反向构建子节点列表,方便遍历。
- lockUser数组:长度为 n,
lockUser[i]
表示节点 i 当前被哪个用户锁定,0 表示未锁。
具体操作实现
- lock(num, user):
-
- 判断
lockUser[num]
是否为0,若不是0表示节点已锁,返回False
。 - 否则,设置
lockUser[num] = user
,返回True
。
- 判断
- unlock(num, user):
-
- 判断
lockUser[num] == user
,只有当前用户才能解锁。 - 成功则设置
lockUser[num] = 0
,返回True
,否则False
。
- 判断
- upgrade(num, user):
-
- 判断节点是否未锁定。
- 判断是否有锁定的祖先。
- 递归遍历子孙,收集锁定的子孙节点。
- 若满足条件则解锁所有这些子孙,并锁定当前节点。
代码示例
from typing import List
class LockingTree:
def __init__(self, parent: List[int]):
self.parent = parent
n = len(parent)
self.children = [[] for _ in range(n)]
for i in range(1, n):
self.children[parent[i]].append(i)
self.lockUser = [0] * n # 0 表示未锁,非0表示被该用户锁定
def lock(self, num: int, user: int) -> bool:
if self.lockUser[num] != 0:
return False
self.lockUser[num] = user
return True
def unlock(self, num: int, user: int) -> bool:
if self.lockUser[num] != user:
return False
self.lockUser[num] = 0
return True
def upgrade(self, num: int, user: int) -> bool:
# 条件1:节点未锁定
if self.lockUser[num] != 0:
return False
# 条件3:无锁定祖先
if self.hasLockedAncestor(num):
return False
# 条件2:至少有一个锁定的子孙
locked_descendants = []
self.collectLockedDescendants(num, locked_descendants)
if not locked_descendants:
return False
# 解锁所有锁定的子孙
for node in locked_descendants:
self.lockUser[node] = 0
# 锁定当前节点
self.lockUser[num] = user
return True
def hasLockedAncestor(self, num: int) -> bool:
p = self.parent[num]
while p != -1:
if self.lockUser[p] != 0:
return True
p = self.parent[p]
return False
def collectLockedDescendants(self, num: int, locked_descendants: List[int]):
for child in self.children[num]:
if self.lockUser[child] != 0:
locked_descendants.append(child)
self.collectLockedDescendants(child, locked_descendants)
复杂度分析
- 初始化:O(n),构建子节点列表。
- lock/unlock:O(1) 时间,直接检查和更新状态。
- upgrade:
-
- 判断祖先锁定状态:O(h),h为树高,最坏O(n)。
- 遍历子孙节点:O(k),k为子孙数,最坏O(n)。
整体由于 n 和操作次数较小,性能满足题目需求。
示例说明
假设有如下树,parent数组为 [-1,0,0,1,1,2,2]
,即:
0
/ \
1 2
/ \ / \
3 4 5 6
操作示例:
lock(2, 2)
:节点2未锁,成功锁定,返回True
。unlock(2, 3)
:节点2被用户2锁定,用户3解锁失败,返回False
。unlock(2, 2)
:成功解锁,返回True
。lock(4, 5)
:节点4未锁,成功锁定,返回True
。upgrade(0, 1)
:
-
- 节点0未锁。
- 节点0没有锁定祖先。
- 节点0的子孙节点4被锁。
- 满足条件,解锁节点4,锁定节点0,返回
True
。
lock(0, 1)
:节点0已经被锁,返回False
。
总结与延展
这道题考察了树结构上的状态维护与递归遍历能力,涉及祖先和子孙节点的条件判断。
- 通过维护父节点和子节点列表,能方便地查询祖先和子孙。
- 递归深度受限于树高,DFS在题目范围内效率足够。
- 可用类似的方法扩展至更复杂的树状结构状态管理。
若想优化:
- 对祖先的锁定状态判断,可以用额外数据结构缓存锁定路径。
- 对子孙锁定节点统计,可借助树状数组或线段树加速查询。
但题目数据规模限制,当前方案已经实用且简洁。