1993. 树上的操作

目录

树上节点加锁系统设计 —— 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),并解锁其所有被锁定的子孙节点。仅当满足以下三个条件时才可执行升级:
    1. 节点 num 当前未被锁定。
    2. 节点 num 至少有一个被锁定的子孙节点(可以是任意用户锁定)。
    3. 节点 num 的所有祖先节点均未被锁定。

满足上述条件时,执行升级操作,返回 true;否则返回 false


题目要求总结

  • 设计一个支持上述三种操作的数据结构。
  • 对树结构的父节点数组进行初始化。
  • 调用 lockunlockupgrade 函数时需高效地满足条件判断。
  • 节点锁定状态和用户信息需要准确维护。
  • 操作次数和节点数均有限制,考虑时间复杂度。

解题分析

本题的核心难点在于如何快速判断:

  • 一个节点是否有锁定的祖先节点。
  • 一个节点的所有子孙中是否存在已锁定的节点。
  • 如何快速找到并解锁所有被锁定的子孙节点。

同时,每次操作都要对锁定状态进行更新。


关键点

  1. 维护锁定状态:用一个数组记录每个节点是否被锁定,以及锁定的用户ID。
  2. 快速判断祖先锁状态
    • 由于父节点信息给出,可以沿着父节点路径向上遍历检查祖先节点是否被锁。
    • 该操作在最坏情况下是树高的时间复杂度,但考虑树最大2000节点,操作最多2000次,可接受。
  1. 快速查找锁定的子孙
    • 通过初始化时,将每个节点的子节点保存下来,构造子节点列表。
    • 使用DFS递归遍历子树,找到所有已锁的子孙。
    • 同样,考虑节点数和操作限制,DFS遍历子孙节点是可行的。
  1. 解锁子孙并锁定当前节点(升级)
    • 在满足条件后,遍历所有锁定的子孙节点进行解锁。
    • 锁定当前节点。

解题方法

数据结构设计

  • 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在题目范围内效率足够。
  • 可用类似的方法扩展至更复杂的树状结构状态管理。

若想优化:

  • 对祖先的锁定状态判断,可以用额外数据结构缓存锁定路径。
  • 对子孙锁定节点统计,可借助树状数组或线段树加速查询。

但题目数据规模限制,当前方案已经实用且简洁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值