The "Design Phone Directory" problem asks you to implement a phone directory that manages a pool of phone numbers. The directory should support three main operations:
get()
: Provide an available number from the directory. If no number is available, return -1
.
check(number)
: Check if a specific number
is available in the directory.
release(number)
: Recycle or release a previously assigned number
back to the pool, making it available again.
The directory is initialized with a maximum number of phone numbers, maxNumbers
. All numbers in the range 0
to maxNumbers - 1
are initially available. You must ensure that:
At first glance, you might consider using a simple array or list to track which numbers are available or assigned. For example, you could use a boolean array where each index represents a number's availability. However, this approach could be inefficient for the get()
operation, as you may need to scan the entire array to find an available number.
To optimize, we need a way to quickly assign and recycle numbers. Ideally, we want get()
, check()
, and release()
to all operate in constant time (O(1)
). This suggests using data structures like a queue (to track available numbers) and a set (to check availability quickly).
The challenge is to ensure that assigned numbers are not given out again until they are released, and that checking and releasing are both efficient.
We will use two main data structures:
get()
and add numbers back on release()
.
check()
. This prevents assigning the same number twice.
The steps are:
0
to maxNumbers - 1
. The set contains all these numbers as well.
-1
.
true
if the number is in the set (i.e., available), false
otherwise.
This design ensures that all operations are efficient and that no number is assigned more than once without being released.
Suppose maxNumbers = 3
. The directory is initialized with numbers 0, 1, 2
available.
get()
returns 0
. Now, available numbers are 1, 2
.
get()
returns 1
. Now, available number is 2
.
check(2)
returns true
(since 2
is available).
get()
returns 2
. Now, no numbers are available.
get()
returns -1
(since all numbers are assigned).
release(1)
makes 1
available again.
get()
returns 1
(since it was just released).
At each step, the set and queue ensure that numbers are not assigned twice, and released numbers become available again.
Brute-force approach: Using only an array or list, get()
could take O(N)
time in the worst case (where N
is the total number of phone numbers), as you may need to scan for an available number.
Optimized approach: With a queue and set:
get()
: O(1)
, as we pop from the queue and remove from the set.
check(number)
: O(1)
, as set lookup is constant time.
release(number)
: O(1)
, as set and queue insertions are constant time.
O(N)
for storing the set and queue of numbers.
This makes the optimized approach very efficient for all required operations.
The Phone Directory problem is elegantly solved using a combination of a queue (to track available numbers) and a set (to enable fast availability checks). This approach ensures all operations—get()
, check()
, and release()
—are performed in constant time. By carefully managing which numbers are available and assigned, we guarantee correctness and efficiency, making this solution both practical and optimal for real-world scenarios.
from collections import deque
class PhoneDirectory:
def __init__(self, maxNumbers: int):
self.available = deque(range(maxNumbers))
self.available_set = set(range(maxNumbers))
def get(self) -> int:
if not self.available:
return -1
number = self.available.popleft()
self.available_set.remove(number)
return number
def check(self, number: int) -> bool:
return number in self.available_set
def release(self, number: int) -> None:
if number not in self.available_set:
self.available.append(number)
self.available_set.add(number)
#include <queue>
#include <unordered_set>
class PhoneDirectory {
private:
std::queue<int> available;
std::unordered_set<int> available_set;
public:
PhoneDirectory(int maxNumbers) {
for (int i = 0; i < maxNumbers; ++i) {
available.push(i);
available_set.insert(i);
}
}
int get() {
if (available.empty()) return -1;
int number = available.front();
available.pop();
available_set.erase(number);
return number;
}
bool check(int number) {
return available_set.count(number) > 0;
}
void release(int number) {
if (available_set.count(number) == 0) {
available.push(number);
available_set.insert(number);
}
}
};
import java.util.*;
class PhoneDirectory {
private Queue<Integer> available;
private Set<Integer> availableSet;
public PhoneDirectory(int maxNumbers) {
available = new LinkedList<>();
availableSet = new HashSet<>();
for (int i = 0; i < maxNumbers; i++) {
available.offer(i);
availableSet.add(i);
}
}
public int get() {
if (available.isEmpty()) return -1;
int number = available.poll();
availableSet.remove(number);
return number;
}
public boolean check(int number) {
return availableSet.contains(number);
}
public void release(int number) {
if (!availableSet.contains(number)) {
available.offer(number);
availableSet.add(number);
}
}
}
class PhoneDirectory {
constructor(maxNumbers) {
this.available = [];
this.availableSet = new Set();
for (let i = 0; i < maxNumbers; i++) {
this.available.push(i);
this.availableSet.add(i);
}
}
get() {
if (this.available.length === 0) return -1;
const number = this.available.shift();
this.availableSet.delete(number);
return number;
}
check(number) {
return this.availableSet.has(number);
}
release(number) {
if (!this.availableSet.has(number)) {
this.available.push(number);
this.availableSet.add(number);
}
}
}