This question has appeared in slightly different Amazon interview forms.
Refer:
https://leetcode.com/discuss/post/1453430/amazon-phone-interview-by-anonymous_user-eq6m/
https://leetcode.com/discuss/post/1031933/amazon-onsite-sde2-package-dependencies-tuw9e/
You are given a map of software packages and dependencies.
Example:
A depends on B, C
B depends on D, E, F
C depends on F
F depends on G
H depends on I, J
J depends on GWrite a class/method that installs an individual package along with all of its dependencies.
Assume every package has an install() method that installs only that package, not its dependencies.
For example, to install package A, a valid install order could be:
G, F, C, E, D, B, AYou are given package dependencies and a package name x.
Return the build order for package x.
Example:
A -> {B, C}
B -> {E}
C -> {D, E, F}
D -> {}
F -> {}
G -> {C}For package A, one valid build order is:
E, B, F, D, C, AThe interviewer may also ask how to handle cyclic dependencies.
This is basically the same as:
The mental model is:
Before installing/building a package,
install/build all of its dependencies first.So DFS fits naturally.
In Course Schedule:
course -> prerequisitesHere:
package -> dependenciesSame idea.
For example:
A -> [B, C]
B -> [D, E, F]
C -> [F]
F -> [G]means:
Before A, install B and C.
Before B, install D, E, F.
Before C, install F.
Before F, install G.So the valid order should put dependencies before the package:
G, F, C, E, D, B, Aor another valid topological order.
The exact order may not be unique.
DFS works well because it follows the dependency chain first.
For package A:
dfs(A)
dfs(B)
dfs(D) -> install/append D
dfs(E) -> install/append E
dfs(F)
dfs(G) -> install/append G
install/append F
install/append B
dfs(C)
dfs(F) already processed
install/append C
install/append ASo the action happens after dependencies are done:
DFS dependency first
↓
Append current package OR call package.install()If the interviewer asks to return build/install order, use:
self.order.append(package)If the interviewer asks to actually install, use:
package.install()Same DFS. Only the final action changes.
Use two sets:
visitingPackages currently in the DFS path.
If we reach a package already in visiting, there is a cycle.
visitedPackages already fully processed/installed.
If a package is in visited, we do not process it again.
Here the input is already an adjacency list:
package -> [dependencies]So we can use it directly.
class PackageInstaller:
def __init__(self, dependencies):
self.dependencies = dependencies # already adjacency list: package -> [dependencies]
self.visiting = set() # current DFS path, used for cycle detection
self.visited = set() # packages already fully processed
self.order = [] # final install/build order
def installWithDependencies(self, package):
if not self.dfs(package):
return [] # cycle found, cannot install/build safely
return self.order
def dfs(self, package):
if package in self.visiting: # cycle: package already in current DFS path
return False
if package in self.visited: # already installed / processed once
return True
self.visiting.add(package) # start checking this package
for dep in self.dependencies.get(package, []): # first install/build all dependencies
if not self.dfs(dep):
return False
self.visiting.remove(package) # done checking this package
self.visited.add(package) # mark package as fully processed
self.order.append(package) # add only after all dependencies are done
return Truedependencies = {
"A": ["B", "C"],
"B": ["D", "E", "F"],
"C": ["F"],
"F": ["G"],
"H": ["I", "J"],
"J": ["G"]
}
installer = PackageInstaller(dependencies)
print(installer.installWithDependencies("A"))One possible output:
["D", "E", "G", "F", "B", "C", "A"]This is valid because:
D, E, F come before B
G comes before F
F comes before C
B and C come before AThe output does not have to exactly match the sample as long as every dependency appears before the package that depends on it.
install()Sometimes the interviewer may say:
Each package already has an
install()method. Install packageAand all of its dependencies.
That means instead of returning a list, we actually call install() on every package in the right order.
The DFS logic is the same:
process all dependencies first
↓
then install current packageSo in the previous code, this line:
self.order.append(package)was used to store the package in the final answer.
For actual installation, replace it with:
package.install()class PackageInstaller:
def __init__(self, dependencies):
self.dependencies = dependencies # package -> [dependencies]
self.visiting = set() # current DFS path, used for cycle detection
self.visited = set() # packages already installed / processed
def installWithDependencies(self, package):
return self.dfs(package) # True if installed safely, False if cycle found
def dfs(self, package):
if package in self.visiting: # cycle: package already in current DFS path
return False
if package in self.visited: # already installed once
return True
self.visiting.add(package)
for dep in self.dependencies.get(package, []): # install dependencies first
if not self.dfs(dep):
return False
self.visiting.remove(package)
self.visited.add(package)
package.install() # actual install happens here, after dependencies
return Trueclass Package:
def __init__(self, name):
self.name = name
def install(self):
print(f"Installing {self.name}")If dependencies are:
A -> [B, C]
B -> [D]
C -> []
D -> []Calling:
installer.installWithDependencies(A)would install in this order:
Installing D
Installing B
Installing C
Installing ASo the only real difference is:
# return-order version
self.order.append(package)vs
# actual-install version
package.install()Same DFS. Different final action.
One caveat: in the actual `install()` version, `package` needs to be a package object, not just a string, because strings don’t have `.install()`.Let:
V = number of packages
E = number of dependency relationshipsTime Complexity:
O(V + E)Each package and dependency edge is processed once.
Space Complexity:
O(V + E)For the dependency map, visited sets, result list, and recursion stack.
This is not really a new problem.
It is Course Schedule II with different names:
course -> package
prereq -> dependency
result -> install/build order
cycle -> invalid dependency chainThe key line is:
self.order.append(package)after processing all dependencies.
If the interviewer wants actual installation, replace that line with:
package.install()