给你一棵 n 个节点的树,编号从 0 到 n - 1 ,以父节点数组 parent 的形式给出,其中 parent[i] 是第 i 个节点的父节点。树的根节点为 0 号节点,所以 parent[0] = -1 ,因为它没有父节点。你想要设计一个数据结构实现树里面对节点的加锁,解锁和升级操作。
数据结构需要支持如下函数:
- Lock:指定用户给指定节点 上锁 ,上锁后其他用户将无法给同一节点上锁。只有当节点处于未上锁的状态下,才能进行上锁操作。
- Unlock:指定用户给指定节点 解锁 ,只有当指定节点当前正被指定用户锁住时,才能执行该解锁操作。
- Upgrade:指定用户给指定节点 上锁 ,并且将该节点的所有子孙节点 解锁 。只有如下 3 个条件 全部 满足时才能执行升级操作:
- 指定节点当前状态为未上锁。
- 指定节点至少有一个上锁状态的子孙节点(可以是 任意 用户上锁的)。
- 指定节点没有任何上锁的祖先节点。
请你实现 LockingTree 类:
LockingTree(int[] parent) 用父节点数组初始化数据结构。
lock(int num, int user) 如果 id 为 user 的用户可以给节点 num 上锁,那么返回 true ,否则返回 false 。如果可以执行此操作,节点 num 会被 id 为 user 的用户 上锁 。
unlock(int num, int user) 如果 id 为 user 的用户可以给节点 num 解锁,那么返回 true ,否则返回 false 。如果可以执行此操作,节点 num 变为 未上锁 状态。
upgrade(int num, int user) 如果 id 为 user 的用户可以给节点 num 升级,那么返回 true ,否则返回 false 。如果可以执行此操作,节点 num 会被 升级 。
示例 1:
输入:
["LockingTree", "lock", "unlock", "unlock", "lock", "upgrade", "lock"]
[[[-1, 0, 0, 1, 1, 2, 2]], [2, 2], [2, 3], [2, 2], [4, 5], [0, 1], [0, 1]]
输出:
[null, true, false, true, true, true, false]解释:
LockingTree lockingTree = new LockingTree([-1, 0, 0, 1, 1, 2, 2]);
lockingTree.lock(2, 2); // 返回 true ,因为节点 2 未上锁。// 节点 2 被用户 2 上锁。
lockingTree.unlock(2, 3); // 返回 false ,因为用户 3 无法解锁被用户 2 上锁的节点。
lockingTree.unlock(2, 2); // 返回 true ,因为节点 2 之前被用户 2 上锁。// 节点 2 现在变为未上锁状态。
lockingTree.lock(4, 5); // 返回 true ,因为节点 4 未上锁。// 节点 4 被用户 5 上锁。
lockingTree.upgrade(0, 1); // 返回 true ,因为节点 0 未上锁且至少有一个被上锁的子孙节点(节点 4)。// 节点 0 被用户 1 上锁,节点 4 变为未上锁。
lockingTree.lock(0, 1); // 返回 false ,因为节点 0 已经被上锁了。
提示:
- n == parent.length
- 2 <= n <= 2000
- 对于 i != 0 ,满足 0 <= parent[i] <= n - 1
- parent[0] == -1
- 0 <= num <= n - 1
- 1 <= user <= 104
- parent 表示一棵合法的树。
- lock ,unlock 和 upgrade 的调用 总共 不超过 2000 次。
解题思路
使用map维护被锁节点和加锁者的关系,再使用一个map维护父节点和对应子节点之间的关系
lock
判断是否有锁,如果没有,则可以直接加锁
unlock
判断当前锁的加锁者是否为自己
upgrade
- 先向上遍历祖先节点和加锁节点,判断是否有加锁
- 再遍历子孙节点,判断是否加锁
- 给所有子孙节点解锁,给当前节点加锁
代码
class LockingTree {int[] p;Map<Integer, Integer> locked = new HashMap<>();Map<Integer,Set<Integer>> child=new HashMap<>();public LockingTree(int[] parent) {p = parent;for (int i = 0; i < parent.length; i++) {if (!child.containsKey(p[i]))child.put(p[i],new HashSet<>());child.get(p[i]).add(i);}}public boolean lock(int num, int user) {if (locked.containsKey(num) )return false;locked.put(num, user);return true;}public boolean unlock(int num, int user) {if (locked.containsKey(num) && locked.get(num) == user) {locked.remove(num);return true;} else return false;}public boolean dfs(int num) {while (num!=-1){if(locked.containsKey(num))return false;num=p[num];}return true;}public boolean ddfs(int num,int f) {if (locked.containsKey(num)&&num!=f){return true;}boolean res=false;if (child.containsKey(num)){for (Integer integer : child.get(num)) {res|=ddfs(integer,f);}}return res;}public void dddfs(int num) {locked.remove(num);if (child.containsKey(num)){for (Integer integer : child.get(num)) {dddfs(integer);}}}public boolean upgrade(int num, int user) {if (dfs(num)&&ddfs(num,num)){dddfs(num);locked.put(num, user);return true;}return false;}}/*** Your LockingTree object will be instantiated and called as such:* LockingTree obj = new LockingTree(parent);* boolean param_1 = obj.lock(num,user);* boolean param_2 = obj.unlock(num,user);* boolean param_3 = obj.upgrade(num,user);*//*** Your LockingTree object will be instantiated and called as such:* LockingTree obj = new LockingTree(parent);* boolean param_1 = obj.lock(num,user);* boolean param_2 = obj.unlock(num,user);* boolean param_3 = obj.upgrade(num,user);*/