Compute minimum-cost service cover
Company: Airbnb
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates proficiency in combinatorial optimization and set-coverage with cost minimization, including handling case-insensitive service matching and enumerating minimal solution combinations.
Constraints
- 0 <= N <= 30, where N is the number of packages
- 0 <= price <= 10^6 for each package
- 0 <= S <= 15, where S is the number of distinct required services after case-insensitive normalization
- The number of optimal combinations can be exponential in N, so reconstruction is inherently output-sensitive
Examples
Input: ([(["wifi"], 50), (["parking"], 30), (["parking", "tv"], 70)], ["WiFi", "parking"])
Expected Output: (80, [[0, 1]])
Explanation: Package 0 covers wifi and package 1 covers parking for a total of 80. Package 2 does not include wifi, so using it with package 0 costs 120 and is not optimal.
Input: ([(["wifi"], 50), (["tv"], 20)], ["wifi", "parking"])
Expected Output: (-1, [])
Explanation: No package provides parking, so it is impossible to cover all required services.
Input: ([(["a"], 5), (["b"], 5), (["a", "b"], 10)], ["A", "B"])
Expected Output: (10, [[0, 1], [2]])
Explanation: Either buy packages 0 and 1 for 10 total, or package 2 alone for 10. Both are optimal.
Input: ([(["wifi"], 50)], [])
Expected Output: (0, [[]])
Explanation: No services are required, so the minimum cost is 0 and the empty combination is optimal.
Input: ([(["WiFi", "wifi"], 10), (["PARKING"], 20), (["wifi", "parking"], 40)], ["wifi", "WiFi", "Parking"])
Expected Output: (30, [[0, 1]])
Explanation: Required services normalize to wifi and parking. Package 0 covers wifi for 10, package 1 covers parking for 20, so the best total is 30.
Solution
def solution(packages, required):
normalized_required = []
seen = set()
for name in required:
key = str(name).lower()
if key not in seen:
seen.add(key)
normalized_required.append(key)
s = len(normalized_required)
bit_index = {name: i for i, name in enumerate(normalized_required)}
n = len(packages)
masks = [0] * n
prices = [0] * n
for i, (services, price) in enumerate(packages):
prices[i] = price
mask = 0
local_seen = set()
for service in services:
key = str(service).lower()
if key in bit_index and key not in local_seen:
mask |= 1 << bit_index[key]
local_seen.add(key)
masks[i] = mask
full_mask = (1 << s) - 1
INF = 10 ** 18
dp = [[INF] * (1 << s) for _ in range(n + 1)]
dp[n][0] = 0
for i in range(n - 1, -1, -1):
pkg_mask = masks[i]
price = prices[i]
next_row = dp[i + 1]
row = dp[i]
for rem in range(1 << s):
best = next_row[rem]
new_rem = rem & ~pkg_mask
take_cost = price + next_row[new_rem]
if take_cost < best:
best = take_cost
row[rem] = best
min_cost = dp[0][full_mask]
if min_cost >= INF:
return (-1, [])
combos = []
path = []
def dfs(i, rem):
if i == n:
if rem == 0:
combos.append(path.copy())
return
best = dp[i][rem]
if dp[i + 1][rem] == best:
dfs(i + 1, rem)
new_rem = rem & ~masks[i]
if prices[i] + dp[i + 1][new_rem] == best:
path.append(i)
dfs(i + 1, new_rem)
path.pop()
dfs(0, full_mask)
combos.sort()
return (min_cost, combos)Time complexity: O(N * 2^S + T), where T is the total size of the reconstructed optimal output. Space complexity: O(N * 2^S + T).
Hints
- Map each distinct required service to a bit position. Then each package becomes a bitmask showing which required services it covers.
- Use DP on (package_index, remaining_services_mask) to compute the minimum cost, then run a DFS that follows only transitions whose cost matches the DP optimum to reconstruct all optimal combos.