Locking and Unlocking of Resources arranged in the form of n-ary Tree
Last Updated :
23 Jul, 2025
Given an n-ary tree of resources arranged hierarchically such that height of tree is O(log n) where n is total number of nodes (or resources). A process needs to lock a resource node in order to use it. But a node cannot be locked if any of its descendant or ancestor is locked.
The operations that need to be supported are:
- islock() – Check if a given resource node is locked or not.
- Lock() – Lock a given resource node if it is not already locked and if none of its ancestors or descendants are locked.
- unLock() – Unlock a given resource node and update its lock information.
The goal is to ensure the following time complexities:
- islock() should be O(1)
- Lock() should be O(log n)
- unLock() should be O(log n)
It is given that resources need to be stored in the form of the n-ary tree. Now the question is, how to augment the tree to achieve the above complexity bounds.
Some General Questions:
Q1. Why doesn't setting Lock = true alone solve the purpose?
Because for the approaches where we move towards the parent to check if locking is possible if a request comes for any node between a locked node and the root, then there's no way of telling that a descendant node is locked. Hence, variables like isLockable will be used to maintain this info.
Q2. Why not lock all nodes from the current node to the root?
Because locking, in general, is a resource-intensive operation and performing locking for all nodes right up to the root would be a waste of resources. Hence, lightweight solutions such as introducing a isLockable variable are used.
[Naive Approach] Checking ancestors and descendants
The idea is to store a boolean variable isLocked for each resource node. The isLocked variable is set to true if the node is locked, and false otherwise.
Key Operations:
- isLock(): Checks the
isLocked status of the given node. - Lock(): If
isLocked is already true, the node cannot be locked. If isLocked is false, the function checks all descendant and ancestor nodes. If any of these nodes are locked, the current node cannot be locked. If no ancestor or descendant node is locked, the node is locked by setting isLocked to true. - unLock(): If
isLocked is false, no action is performed. If isLocked is true, the node is unlocked by setting isLocked to false.
C++
#include <iostream>
#include <vector>
using namespace std;
class NaryTree
{
public:
// True if the node is locked
bool isLocked;
// Pointer to parent node
NaryTree *parent;
// List of child nodes
vector<NaryTree *> children;
NaryTree(NaryTree *parent = nullptr) : parent(parent), isLocked(false)
{
}
void addChild(NaryTree *child)
{
children.push_back(child);
child->parent = this;
}
};
bool isLock(NaryTree *node)
{
return node->isLocked;
}
bool hasLockedDescendants(NaryTree *node)
{
for (NaryTree *child : node->children)
{
if (child->isLocked || hasLockedDescendants(child))
{
return true;
}
}
return false;
}
bool Lock(NaryTree *node)
{
// Node is already locked
if (node->isLocked)
{
return false;
}
// Check all ancestors for locks
NaryTree *current = node->parent;
while (current != nullptr)
{
if (current->isLocked)
{
return false;
}
current = current->parent;
}
// Check all descendants for locks
if (hasLockedDescendants(node))
{
return false;
}
// Lock the node
node->isLocked = true;
return true;
}
// Unlock function simply sets isLocked to false (O(1))
void unLock(NaryTree *node)
{
node->isLocked = false;
}
int main()
{
NaryTree root;
NaryTree child1(&root);
NaryTree child2(&root);
NaryTree child3(&child1);
NaryTree child4(&child1);
// Add children to the nodes
root.addChild(&child1);
root.addChild(&child2);
child1.addChild(&child3);
child1.addChild(&child4);
// Test lock and unlock functionality
// Lock child1
if (Lock(&child1))
{
cout << "child1 locked successfully." << endl;
}
else
{
cout << "Failed to lock child1." << endl;
}
// Try to lock child3, should fail since child1 is locked
if (Lock(&child3))
{
cout << "child3 locked successfully." << endl;
}
else
{
cout << "Failed to lock child3." << endl;
}
// Unlock child1
unLock(&child1);
cout << "child1 unlocked." << endl;
// Try to lock child3 again, should succeed now
if (Lock(&child3))
{
cout << "child3 locked successfully." << endl;
}
else
{
cout << "Failed to lock child3." << endl;
}
return 0;
}
Java
import java.util.ArrayList;
import java.util.List;
class NaryTree {
// True if the node is locked
boolean isLocked;
// Pointer to parent node
NaryTree parent;
// List of child nodes
List<NaryTree> children;
NaryTree(NaryTree parent)
{
this.parent = parent;
this.isLocked = false;
this.children = new ArrayList<>();
}
void addChild(NaryTree child)
{
children.add(child);
child.parent = this;
}
}
class NaryTreeLock {
static boolean isLock(NaryTree node)
{
return node.isLocked;
}
static boolean hasLockedDescendants(NaryTree node)
{
for (NaryTree child : node.children) {
if (child.isLocked
|| hasLockedDescendants(child)) {
return true;
}
}
return false;
}
static boolean Lock(NaryTree node)
{
// Node is already locked
if (node.isLocked) {
return false;
}
// Check all ancestors for locks
NaryTree current = node.parent;
while (current != null) {
if (current.isLocked) {
return false;
}
current = current.parent;
}
// Check all descendants for locks
if (hasLockedDescendants(node)) {
return false;
}
// Lock the node
node.isLocked = true;
return true;
}
// Unlock function simply sets isLocked to false (O(1))
static void unLock(NaryTree node)
{
node.isLocked = false;
}
public static void main(String[] args)
{
NaryTree root = new NaryTree(null);
NaryTree child1 = new NaryTree(root);
NaryTree child2 = new NaryTree(root);
NaryTree child3 = new NaryTree(child1);
NaryTree child4 = new NaryTree(child1);
// Add children to the nodes
root.addChild(child1);
root.addChild(child2);
child1.addChild(child3);
child1.addChild(child4);
// Test lock and unlock functionality
// Lock child1
if (Lock(child1)) {
System.out.println(
"child1 locked successfully.");
}
else {
System.out.println("Failed to lock child1.");
}
// Try to lock child3, should fail since child1 is
// locked
if (Lock(child3)) {
System.out.println(
"child3 locked successfully.");
}
else {
System.out.println("Failed to lock child3.");
}
// Unlock child1
unLock(child1);
System.out.println("child1 unlocked.");
// Try to lock child3 again, should succeed now
if (Lock(child3)) {
System.out.println(
"child3 locked successfully.");
}
else {
System.out.println("Failed to lock child3.");
}
}
}
Python
class NaryTree:
# True if the node is locked
def __init__(self, parent=None):
self.isLocked = False
self.parent = parent
self.children = []
def addChild(self, child):
self.children.append(child)
child.parent = self
def isLock(node):
return node.isLocked
def hasLockedDescendants(node):
for child in node.children:
if child.isLocked or hasLockedDescendants(child):
return True
return False
def Lock(node):
# Node is already locked
if node.isLocked:
return False
# Check all ancestors for locks
current = node.parent
while current:
if current.isLocked:
return False
current = current.parent
# Check all descendants for locks
if hasLockedDescendants(node):
return False
# Lock the node
node.isLocked = True
return True
# Unlock function simply sets isLocked to false (O(1))
def unLock(node):
node.isLocked = False
if __name__ == '__main__':
root = NaryTree()
child1 = NaryTree(root)
child2 = NaryTree(root)
child3 = NaryTree(child1)
child4 = NaryTree(child1)
# Add children to the nodes
root.addChild(child1)
root.addChild(child2)
child1.addChild(child3)
child1.addChild(child4)
# Test lock and unlock functionality
# Lock child1
if Lock(child1):
print("child1 locked successfully.")
else:
print("Failed to lock child1.")
# Try to lock child3, should fail since child1 is locked
if Lock(child3):
print("child3 locked successfully.")
else:
print("Failed to lock child3.")
# Unlock child1
unLock(child1)
print("child1 unlocked.")
# Try to lock child3 again, should succeed now
if Lock(child3):
print("child3 locked successfully.")
else:
print("Failed to lock child3.")
C#
using System;
using System.Collections.Generic;
public class NaryTree {
// True if the node is locked
public bool IsLocked;
// Pointer to parent node
public NaryTree Parent;
// List of child nodes
public List<NaryTree> Children;
public NaryTree(NaryTree parent = null)
{
Parent = parent;
IsLocked = false;
Children = new List<NaryTree>();
}
public void AddChild(NaryTree child)
{
Children.Add(child);
child.Parent = this;
}
}
public class Program {
public static bool IsLock(NaryTree node)
{
return node.IsLocked;
}
public static bool HasLockedDescendants(NaryTree node)
{
foreach(NaryTree child in node.Children)
{
if (child.IsLocked
|| HasLockedDescendants(child)) {
return true;
}
}
return false;
}
public static bool Lock(NaryTree node)
{
// Node is already locked
if (node.IsLocked) {
return false;
}
// Check all ancestors for locks
NaryTree current = node.Parent;
while (current != null) {
if (current.IsLocked) {
return false;
}
current = current.Parent;
}
// Check all descendants for locks
if (HasLockedDescendants(node)) {
return false;
}
// Lock the node
node.IsLocked = true;
return true;
}
// Unlock function simply sets IsLocked to false (O(1))
public static void UnLock(NaryTree node)
{
node.IsLocked = false;
}
public static void Main()
{
NaryTree root = new NaryTree();
NaryTree child1 = new NaryTree(root);
NaryTree child2 = new NaryTree(root);
NaryTree child3 = new NaryTree(child1);
NaryTree child4 = new NaryTree(child1);
// Add children to the nodes
root.AddChild(child1);
root.AddChild(child2);
child1.AddChild(child3);
child1.AddChild(child4);
// Test lock and unlock functionality
// Lock child1
if (Lock(child1)) {
Console.WriteLine(
"child1 locked successfully.");
}
else {
Console.WriteLine("Failed to lock child1.");
}
// Try to lock child3, should fail since child1 is
// locked
if (Lock(child3)) {
Console.WriteLine(
"child3 locked successfully.");
}
else {
Console.WriteLine("Failed to lock child3.");
}
// Unlock child1
UnLock(child1);
Console.WriteLine("child1 unlocked.");
// Try to lock child3 again, should succeed now
if (Lock(child3)) {
Console.WriteLine(
"child3 locked successfully.");
}
else {
Console.WriteLine("Failed to lock child3.");
}
}
}
JavaScript
class NaryTree {
// True if the node is locked
constructor(parent = null)
{
this.isLocked = false;
this.parent = parent;
this.children = [];
}
addChild(child)
{
this.children.push(child);
child.parent = this;
}
}
function isLock(node) { return node.isLocked; }
function hasLockedDescendants(node)
{
for (const child of node.children) {
if (child.isLocked || hasLockedDescendants(child)) {
return true;
}
}
return false;
}
function Lock(node)
{
// Node is already locked
if (node.isLocked) {
return false;
}
// Check all ancestors for locks
let current = node.parent;
while (current !== null) {
if (current.isLocked) {
return false;
}
current = current.parent;
}
// Check all descendants for locks
if (hasLockedDescendants(node)) {
return false;
}
// Lock the node
node.isLocked = true;
return true;
}
// Unlock function simply sets isLocked to false (O(1))
function unLock(node) { node.isLocked = false; }
function main()
{
const root = new NaryTree();
const child1 = new NaryTree(root);
const child2 = new NaryTree(root);
const child3 = new NaryTree(child1);
const child4 = new NaryTree(child1);
// Add children to the nodes
root.addChild(child1);
root.addChild(child2);
child1.addChild(child3);
child1.addChild(child4);
// Test lock and unlock functionality
// Lock child1
if (Lock(child1)) {
console.log("child1 locked successfully.");
}
else {
console.log("Failed to lock child1.");
}
// Try to lock child3, should fail since child1 is
// locked
if (Lock(child3)) {
console.log("child3 locked successfully.");
}
else {
console.log("Failed to lock child3.");
}
// Unlock child1
unLock(child1);
console.log("child1 unlocked.");
// Try to lock child3 again, should succeed now
if (Lock(child3)) {
console.log("child3 locked successfully.");
}
else {
console.log("Failed to lock child3.");
}
}
main();
Outputchild1 locked successfully.
Failed to lock child3.
child1 unlocked.
child3 locked successfully.
Time Complexity:
- isLock() O(1)
- Lock() O(n), because there can be O(n) descendants.
- unLock() O(1)
Auxiliary Space: O(1)
[Expected Approach] Using counter variable
The idea is to augment tree with following three fields:
- A boolean field isLocked (same as above method).
- Parent-Pointer to access all ancestors in O(Log n) time.
- Count-of-locked-descendants A node can be ancestor of many descendants. We can check if any of the descendants is locked using this count.
Key Operations:
isLock(): Returns the current lock status of the node.Lock(): This operation checks all ancestors (up to O(log n) ancestors) to ensure no ancestor is locked, and ensures no descendants are locked using the lockedDescendantsCount. If everything is clear, it locks the node and increments the lockedDescendantsCount for all ancestors.unLock(): This operation sets the node's isLocked to false, and decrements the lockedDescendantsCount for all ancestors as the node is no longer considered a locked descendant in log(n).
C++
#include <iostream>
#include <vector>
using namespace std;
class NaryTree
{
public:
// True if the node is locked
bool isLocked;
// Pointer to parent node
NaryTree *parent;
// List of child nodes (n-ary tree structure)
vector<NaryTree *> children;
// Count of locked descendants in the subtree
int lockedDescendantsCount;
NaryTree(NaryTree *parent = nullptr) : parent(parent), isLocked(false), lockedDescendantsCount(0)
{
}
void addChild(NaryTree *child)
{
children.push_back(child);
child->parent = this;
}
bool isLock()
{
return isLocked;
}
bool Lock()
{
// Node is already locked or has locked descendants
if (isLocked || lockedDescendantsCount > 0)
{
return false;
}
// Check all ancestors for locks
NaryTree *current = parent;
while (current != nullptr)
{
// If any ancestor is locked, this node cannot be locked
if (current->isLocked)
{
return false;
}
current = current->parent;
}
// Lock the node
isLocked = true;
// Update locked descendants count for all ancestors
current = parent;
while (current != nullptr)
{
current->lockedDescendantsCount++;
current = current->parent;
}
return true;
}
void unLock()
{
// Node is not locked, nothing to do
if (!isLocked)
{
return;
}
// Unlock the node
isLocked = false;
// Update locked descendants count for all ancestors
NaryTree *current = parent;
while (current != nullptr)
{
current->lockedDescendantsCount--;
current = current->parent;
}
}
};
int main()
{
NaryTree root;
NaryTree child1(&root);
NaryTree child2(&root);
NaryTree child3(&child1);
NaryTree child4(&child1);
// Add children to root and child1
root.addChild(&child1);
root.addChild(&child2);
child1.addChild(&child3);
child1.addChild(&child4);
// Lock child1
if (child1.Lock())
{
cout << "child1 locked successfully." << endl;
}
else
{
cout << "Failed to lock child1." << endl;
}
// Try to lock child3, should fail since child1 is locked
if (child3.Lock())
{
cout << "child3 locked successfully." << endl;
}
else
{
cout << "Failed to lock child3." << endl;
}
// Unlock child1
child1.unLock();
cout << "child1 unlocked." << endl;
// Try to lock child3 again, should succeed now
if (child3.Lock())
{
cout << "child3 locked successfully." << endl;
}
else
{
cout << "Failed to lock child3." << endl;
}
return 0;
}
Java
import java.util.ArrayList;
import java.util.List;
class NaryTree {
// True if the node is locked
private boolean isLocked;
// Pointer to parent node
private NaryTree parent;
// List of child nodes (n-ary tree structure)
private List<NaryTree> children;
// Count of locked descendants in the subtree
private int lockedDescendantsCount;
public NaryTree(NaryTree parent)
{
this.parent = parent;
this.isLocked = false;
this.lockedDescendantsCount = 0;
this.children = new ArrayList<>();
}
public NaryTree() { this(null); }
public void addChild(NaryTree child)
{
children.add(child);
child.parent = this;
}
public boolean isLock() { return isLocked; }
public boolean Lock()
{
// Node is already locked or has locked descendants
if (isLocked || lockedDescendantsCount > 0) {
return false;
}
// Check all ancestors for locks
NaryTree current = parent;
while (current != null) {
// If any ancestor is locked, this node cannot
// be locked
if (current.isLocked) {
return false;
}
current = current.parent;
}
// Lock the node
isLocked = true;
// Update locked descendants count for all ancestors
current = parent;
while (current != null) {
current.lockedDescendantsCount++;
current = current.parent;
}
return true;
}
public void unLock()
{
// Node is not locked, nothing to do
if (!isLocked) {
return;
}
// Unlock the node
isLocked = false;
// Update locked descendants count for all ancestors
NaryTree current = parent;
while (current != null) {
current.lockedDescendantsCount--;
current = current.parent;
}
}
public static void main(String[] args)
{
NaryTree root = new NaryTree();
NaryTree child1 = new NaryTree(root);
NaryTree child2 = new NaryTree(root);
NaryTree child3 = new NaryTree(child1);
NaryTree child4 = new NaryTree(child1);
// Add children to root and child1
root.addChild(child1);
root.addChild(child2);
child1.addChild(child3);
child1.addChild(child4);
// Lock child1
if (child1.Lock()) {
System.out.println(
"child1 locked successfully.");
}
else {
System.out.println("Failed to lock child1.");
}
// Try to lock child3, should fail since child1 is
// locked
if (child3.Lock()) {
System.out.println(
"child3 locked successfully.");
}
else {
System.out.println("Failed to lock child3.");
}
// Unlock child1
child1.unLock();
System.out.println("child1 unlocked.");
// Try to lock child3 again, should succeed now
if (child3.Lock()) {
System.out.println(
"child3 locked successfully.");
}
else {
System.out.println("Failed to lock child3.");
}
}
}
Python
class NaryTree:
def __init__(self, parent=None):
self.isLocked = False # True if the node is locked
self.parent = parent # Pointer to parent node
self.children = [] # List of child nodes (n-ary tree structure)
self.lockedDescendantsCount = 0 # Count of locked descendants in the subtree
def addChild(self, child):
self.children.append(child)
child.parent = self
def isLock(self):
return self.isLocked
def Lock(self):
# Node is already locked or has locked descendants
if self.isLocked or self.lockedDescendantsCount > 0:
return False
# Check all ancestors for locks
current = self.parent
while current is not None:
# If any ancestor is locked, this node cannot be locked
if current.isLocked:
return False
current = current.parent
# Lock the node
self.isLocked = True
# Update locked descendants count for all ancestors
current = self.parent
while current is not None:
current.lockedDescendantsCount += 1
current = current.parent
return True
def unLock(self):
# Node is not locked, nothing to do
if not self.isLocked:
return
# Unlock the node
self.isLocked = False
# Update locked descendants count for all ancestors
current = self.parent
while current is not None:
current.lockedDescendantsCount -= 1
current = current.parent
if __name__ == '__main__':
root = NaryTree()
child1 = NaryTree(root)
child2 = NaryTree(root)
child3 = NaryTree(child1)
child4 = NaryTree(child1)
# Add children to root and child1
root.addChild(child1)
root.addChild(child2)
child1.addChild(child3)
child1.addChild(child4)
# Lock child1
if child1.Lock():
print('child1 locked successfully.')
else:
print('Failed to lock child1.')
# Try to lock child3, should fail since child1 is locked
if child3.Lock():
print('child3 locked successfully.')
else:
print('Failed to lock child3.')
# Unlock child1
child1.unLock()
print('child1 unlocked.')
# Try to lock child3 again, should succeed now
if child3.Lock():
print('child3 locked successfully.')
else:
print('Failed to lock child3.')
C#
using System;
using System.Collections.Generic;
public class NaryTree {
// True if the node is locked
public bool isLocked;
// Pointer to parent node
public NaryTree parent;
// List of child nodes (n-ary tree structure)
public List<NaryTree> children;
// Count of locked descendants in the subtree
public int lockedDescendantsCount;
public NaryTree(NaryTree parent = null)
{
this.parent = parent;
this.isLocked = false;
this.lockedDescendantsCount = 0;
this.children = new List<NaryTree>();
}
public void AddChild(NaryTree child)
{
children.Add(child);
child.parent = this;
}
public bool IsLocked() { return isLocked; }
public bool Lock()
{
// Node is already locked or has locked descendants
if (isLocked || lockedDescendantsCount > 0) {
return false;
}
// Check all ancestors for locks
NaryTree current = parent;
while (current != null) {
// If any ancestor is locked, this node cannot
// be locked
if (current.isLocked) {
return false;
}
current = current.parent;
}
// Lock the node
isLocked = true;
// Update locked descendants count for all ancestors
current = parent;
while (current != null) {
current.lockedDescendantsCount++;
current = current.parent;
}
return true;
}
public void Unlock()
{
// Node is not locked, nothing to do
if (!isLocked) {
return;
}
// Unlock the node
isLocked = false;
// Update locked descendants count for all ancestors
NaryTree current = parent;
while (current != null) {
current.lockedDescendantsCount--;
current = current.parent;
}
}
}
public class Program {
public static void Main()
{
NaryTree root = new NaryTree();
NaryTree child1 = new NaryTree(root);
NaryTree child2 = new NaryTree(root);
NaryTree child3 = new NaryTree(child1);
NaryTree child4 = new NaryTree(child1);
// Add children to root and child1
root.AddChild(child1);
root.AddChild(child2);
child1.AddChild(child3);
child1.AddChild(child4);
// Lock child1
if (child1.Lock()) {
Console.WriteLine(
"child1 locked successfully.");
}
else {
Console.WriteLine("Failed to lock child1.");
}
// Try to lock child3, should fail since child1 is
// locked
if (child3.Lock()) {
Console.WriteLine(
"child3 locked successfully.");
}
else {
Console.WriteLine("Failed to lock child3.");
}
// Unlock child1
child1.Unlock();
Console.WriteLine("child1 unlocked.");
// Try to lock child3 again, should succeed now
if (child3.Lock()) {
Console.WriteLine(
"child3 locked successfully.");
}
else {
Console.WriteLine("Failed to lock child3.");
}
}
}
JavaScript
class NaryTree {
// True if the node is locked
constructor(parent = null)
{
this.isLocked = false;
this.parent = parent;
this.children = [];
this.lockedDescendantsCount = 0;
}
addChild(child)
{
this.children.push(child);
child.parent = this;
}
isLock() { return this.isLocked; }
Lock()
{
// Node is already locked or has locked descendants
if (this.isLocked
|| this.lockedDescendantsCount > 0) {
return false;
}
// Check all ancestors for locks
let current = this.parent;
while (current !== null) {
// If any ancestor is locked, this node cannot
// be locked
if (current.isLocked) {
return false;
}
current = current.parent;
}
// Lock the node
this.isLocked = true;
// Update locked descendants count for all ancestors
current = this.parent;
while (current !== null) {
current.lockedDescendantsCount++;
current = current.parent;
}
return true;
}
unLock()
{
// Node is not locked, nothing to do
if (!this.isLocked) {
return;
}
// Unlock the node
this.isLocked = false;
// Update locked descendants count for all ancestors
let current = this.parent;
while (current !== null) {
current.lockedDescendantsCount--;
current = current.parent;
}
}
}
// Main function
const main = () => {
const root = new NaryTree();
const child1 = new NaryTree(root);
const child2 = new NaryTree(root);
const child3 = new NaryTree(child1);
const child4 = new NaryTree(child1);
// Add children to root and child1
root.addChild(child1);
root.addChild(child2);
child1.addChild(child3);
child1.addChild(child4);
// Lock child1
if (child1.Lock()) {
console.log("child1 locked successfully.");
}
else {
console.log("Failed to lock child1.");
}
// Try to lock child3, should fail since child1 is
// locked
if (child3.Lock()) {
console.log("child3 locked successfully.");
}
else {
console.log("Failed to lock child3.");
}
// Unlock child1
child1.unLock();
console.log("child1 unlocked.");
// Try to lock child3 again, should succeed now
if (child3.Lock()) {
console.log("child3 locked successfully.");
}
else {
console.log("Failed to lock child3.");
}
};
main();
Outputchild1 locked successfully.
Failed to lock child3.
child1 unlocked.
child3 locked successfully.
Time Complexity:
- isLock() O(1)
- Lock() O(log n)
- unLock() O(log n)
Auxiliary Space: O(1)
Explore
DSA Fundamentals
Data Structures
Algorithms
Advanced
Interview Preparation
Practice Problem