Find earliest supporting dependency version
Company: OpenAI
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates a candidate's ability to design efficient search algorithms and reason about algorithmic complexity under constraints such as non-monotonic predicate behavior and a rate-limited black-box API.
Constraints
- 0 <= len(versions) <= 200000
- versions is sorted in ascending semantic-version order, all versions are distinct, and supported_versions is a subset of versions
- The support pattern obeys the hierarchical guarantees described above: a first supporting major exists if support exists at all; within it, a first supporting minor exists; within it, a first supporting patch exists; and the latest listed version of any supporting major or minor is also supporting
Examples
Input: (["103.003.01", "103.003.02", "103.004.01", "104.000.01", "104.001.01", "104.001.02", "105.000.01"], ["103.004.01", "104.001.02", "105.000.01"])
Expected Output: "103.004.01"
Explanation: Major 103 is the first major whose latest version supports the feature. Inside it, minor 004 is the first supporting minor, and its only listed patch is the answer.
Input: (["101.000.01", "101.001.01", "102.000.01", "102.000.02", "102.001.01", "103.000.01"], ["102.000.02", "102.001.01", "103.000.01"])
Expected Output: "102.000.02"
Explanation: No version in major 101 supports the feature. In major 102, the first supporting minor is 000, and within that minor the first supporting patch is 02.
Input: (["200.000.01"], ["200.000.01"])
Expected Output: "200.000.01"
Explanation: The only version is supported, so it is also the earliest supporting version.
Input: (["300.000.01", "300.000.02"], [])
Expected Output: None
Explanation: No version supports the feature, so the correct return value is None.
Input: ([], [])
Expected Output: None
Explanation: An empty version list contains no supporting version.
Solution
def solution(versions, supported_versions):
if not versions:
return None
supported = set(supported_versions)
major_order = []
major_map = {}
for v in versions:
major_str, minor_str, _ = v.split('.')
major = int(major_str)
minor = int(minor_str)
if major not in major_map:
major_order.append(major)
major_map[major] = {'latest': v, 'minor_order': [], 'minors': {}}
major_info = major_map[major]
major_info['latest'] = v
if minor not in major_info['minors']:
major_info['minor_order'].append(minor)
major_info['minors'][minor] = {'latest': v, 'versions': []}
minor_info = major_info['minors'][minor]
minor_info['latest'] = v
minor_info['versions'].append(v)
def first_true(items, predicate):
lo, hi = 0, len(items) - 1
answer = None
while lo <= hi:
mid = (lo + hi) // 2
item = items[mid]
if predicate(item):
answer = item
hi = mid - 1
else:
lo = mid + 1
return answer
first_major = first_true(major_order, lambda major: major_map[major]['latest'] in supported)
if first_major is None:
return None
major_info = major_map[first_major]
first_minor = first_true(major_info['minor_order'], lambda minor: major_info['minors'][minor]['latest'] in supported)
if first_minor is None:
return None
patch_versions = major_info['minors'][first_minor]['versions']
lo, hi = 0, len(patch_versions) - 1
answer = None
while lo <= hi:
mid = (lo + hi) // 2
version = patch_versions[mid]
if version in supported:
answer = version
hi = mid - 1
else:
lo = mid + 1
return answerTime complexity: O(n) preprocessing + O(log M + log m + log p) support checks, where M is the number of majors, m the number of minors in the chosen major, and p the number of patches in the chosen minor. Space complexity: O(n).
Hints
- You do not need to probe every version. The latest version inside a major tells you whether that major contains any supporting version.
- After finding the first supporting major, repeat the same idea for minors, then binary search patches inside the chosen minor.