Identify all bad nodes with group tests
Company: xAI
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates a candidate's skills in combinatorial group testing, parallel algorithm design under node-disjoint constraints, and complexity reasoning for identifying faulty elements.
Constraints
- 0 <= len(nodes) <= 200000
- Each nodes[i] is either 0 or 1
- If len(nodes) == 0, then known_good == -1
- If len(nodes) > 0, then 0 <= known_good < len(nodes) and nodes[known_good] == 1
Examples
Input: ([1, 1, 0, 1, 0, 1], 0)
Expected Output: ([2, 4], 9)
Explanation: U = [1, 2, 3, 4, 5]. The full block fails, then the recursion explores both halves. Only indices 2 and 4 end up as failing singletons. The exact strategy performs 9 tests.
Input: ([1, 1, 1, 1], 2)
Expected Output: ([], 1)
Explanation: U = [0, 1, 3]. The first test on the entire block passes, so all remaining nodes are good and recursion stops immediately.
Input: ([], -1)
Expected Output: ([], 0)
Explanation: There are no nodes, so U is empty and no tests are performed.
Input: ([1], 0)
Expected Output: ([], 0)
Explanation: The only node is the known good node, so U is empty and there is nothing to test.
Input: ([0, 1, 1, 0, 1], 2)
Expected Output: ([0, 3], 7)
Explanation: U = [0, 1, 3, 4]. The full block fails, both halves are explored, and indices 0 and 3 are the failing singletons. The exact recursion makes 7 tests.
Input: ([0, 1], 1)
Expected Output: ([0], 1)
Explanation: U = [0]. The singleton block fails immediately, so index 0 is bad and exactly 1 test is used.
Solution
def solution(nodes, known_good):
n = len(nodes)
if n == 0:
return ([], 0)
u = [i for i in range(n) if i != known_good]
m = len(u)
if m == 0:
return ([], 0)
prefix_bad = [0] * (m + 1)
for i, idx in enumerate(u):
prefix_bad[i + 1] = prefix_bad[i] + (1 if nodes[idx] == 0 else 0)
bad_indices = []
test_calls = 0
def has_bad(l, r):
return prefix_bad[r] - prefix_bad[l] > 0
def dfs(l, r):
nonlocal test_calls
if l >= r:
return
test_calls += 1
if not has_bad(l, r):
return
if r - l == 1:
bad_indices.append(u[l])
return
mid = l + (r - l) // 2
dfs(l, mid)
dfs(mid, r)
dfs(0, m)
return (bad_indices, test_calls)
Time complexity: O(n). Space complexity: O(n).
Hints
- A known-good node turns test({known_good} ∪ B) into an all-good check for the whole block B.
- If a block fails, do not inspect every element immediately. Split the block in half and recurse; a failing block of size 1 pinpoints a bad node.