文章目录
- Tag
- 题目来源
- 题目解读
- 解题思路
- 方法一:深度优先搜索
- 写在最后
Tag
【深度优先搜索】【树】【设计数据结构】【2023-09-23】
题目来源
1993. 树上的操作
题目解读
本题是一个设计类的题目,对于设计类的题目就一步步的实现题目要求的成员方法即可。
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
会被升级 。
判断节点 num
是否可以升级有三个要求:
- 节点
num
当前状态为未上锁; - 节点
num
至少有一个未上锁的子孙节点; - 节点
num
没有任何上锁的祖先节点。
解题思路
今天又是认真学习 官方题解 的一天!
方法一:深度优先搜索
需要给节点上锁与解锁,但是需要先判断节点 num
的状态,于是可以想到使用一个数组 lockNodeUser
来记录节点的状态:
- 如果
lockNodeUser[num] = -1
,表示节点num
出于未上锁状态; - 如果
lockNodeUser[num]
等于某个非-1
的数,表示节点num
处于某个用户的锁定状态。
实现成员方法 lock()
如果 lockNodeUser[num] = -1
,表示节点 num
出于未上锁状态,我们可以通过给 lockNodeUser[num]
赋值的方式来实现上锁,并返回 true
。否则,直接返回 false
,因为当前当前节点已经被上锁了,无法再次上锁。
实现成员方法 unlock()
如果 lockNodeUser[num]
等于指定 user
,表示节点 num
处于用户 user
的锁定状态,我们可以进行解锁即给 lockNodeUser[num]
赋值 -1
,并返回 true
;否则表示用户 user
不能给该节点解锁,直接返回 false
。
实现成员方法 upgrade()
升级操作的成员方法实现起来相对复杂,首先需要判断三个条件是否同时满足,如果是,还需要给节点 num
上锁并把它所有的子孙节点解锁。
首先,看第一个条件,需要判断节点 num
是否处于未上述状态,这个很容易判断,通过 lockNodeUser[num]
是否等于 -1
即可判断:若等于说明处于未上锁状态,否则处于上锁状态。
第二个条件要求节点 num
没有任何上锁的祖先节点,这个也好判断,从节点 num
自底向上搜索 lockNodeUser[parent[num]]
的值:
- 如果不等于
-1
,说明有个祖先上锁了; - 若一直向上搜索到根节点了一直都等于
-1
,说明节点num
没有任何祖先节点上锁。
第三个条件节点 num
是否至少有一个上锁的子孙节点,我们将这一条放在最后来判断是希望和这三个条件都满足时需要执行的 “给节点 num
的所有子孙节点解锁一起实现”。我们定义一个递归函数 checkAndUnlockDescendant()
来实现判断加实现。如果第三个条件也满足了,我们通过递归函数实现了 “给节点 num
的所有子孙节点解锁”;如果第三个条件不满足了,我们也通过递归函数实现了 “给节点 num
的所有子孙节点解锁”,这样不会出错吗?当第三步为假,就说明指定节点没有上锁的子孙节点,那么我们仍可以进行「给它的所有子孙节点解锁」这一步,就相当于此时的解锁是无效操作了。
实现第三个条件的递归函数返回一个布尔值表示当前节点是否有上锁的子孙节点(也包括自己),同时将所有的子孙节点(也包括自己)解锁。首先,通过 lockNodeUser[num] != -1
来判断,是否上锁,如果该不等式成立表示上锁,然后解锁,并递归给子孙解锁。
实现代码
class LockingTree {
private:vector<int> parent;vector<int>lockNodeUser;vector<vector<int>> children;public:LockingTree(vector<int>& parent) {int n = parent.size();this->parent = parent;this->lockNodeUser = vector<int>(n, -1); // 初始化上锁状态this->children = vector<vector<int>>(n); // 记录每个父亲的儿子(注意不是所有子孙)for (int i = 0; i < n; ++i) {int p = parent[i];if (p != -1)children[p].push_back(i);}}bool lock(int num, int user) {if (lockNodeUser[num] == -1) {lockNodeUser[num] = user;return true;}return false;}bool unlock(int num, int user) {if (lockNodeUser[num] == user) {lockNodeUser[num] = -1;return true;}return false;}bool hasLockedAncestor(int num) {num = parent[num];while (num != -1) {if (lockNodeUser[num] != -1) {return true;}num = parent[num];}return false;}bool checkAndUnlockDescendant(int num) {bool res = lockNodeUser[num] != -1;lockNodeUser[num] = -1;for (int child : children[num]) {res |= checkAndUnlockDescendant(child);}return res;}bool upgrade(int num, int user) {bool res = lockNodeUser[num] == -1 && !hasLockedAncestor(num) && checkAndUnlockDescendant(num);if (res) {lockNodeUser[num] = user;} return res;}
};/*** Your LockingTree object will be instantiated and called as such:* LockingTree* obj = new LockingTree(parent);* bool param_1 = obj->lock(num,user);* bool param_2 = obj->unlock(num,user);* bool param_3 = obj->upgrade(num,user);*/
复杂度分析
时间复杂度:初始化:构建 children
消耗 O ( n ) O(n) O(n),Lock
和 Unlock
都消耗 O ( 1 ) O(1) O(1),Upgrade
消耗 O ( n ) O(n) O(n)。
空间复杂度:初始化消耗 O ( n ) O(n) O(n),Lock
和 Unlock
都消耗 O ( 1 ) O(1) O(1),Upgrade
消耗 O ( n ) O(n) O(n)。
写在最后
如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。
如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。
最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。