Find earliest supporting version under constraints
Company: OpenAI
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates parsing and comparison of zero-padded version strings, handling of invalid inputs and duplicates, and design of rate-limited search strategies with complexity analysis, reflecting skills in robust input handling, algorithmic thinking, and system-aware optimization.
Part 1: Earliest Supporting Version in an Arbitrary List
Constraints
- 0 <= len(versions) <= 200000
- 0 <= len(supported_versions) <= 200000
- A valid version has exactly 3 non-empty numeric segments separated by dots
- Leading zeros are allowed and do not affect ordering
- Invalid version strings must be ignored
Examples
Input: (["103.003.03", "103.003.02", "102.010.009"], ["103.003.02", "102.010.009"])
Expected Output: "102.10.9"
Explanation: Both listed versions support the feature, and `(102, 10, 9)` is smaller than `(103, 3, 2)`.
Input: (["001.002.003", "1.2.3", "bad", "1.2", "01.002.004"], ["0001.0002.0003", "99.0.0"])
Expected Output: "1.2.3"
Explanation: Invalid entries are ignored. `001.002.003` and `1.2.3` are the same numeric version, which is supported.
Input: (["1.0.0", "2.0.0"], ["3.0.0"])
Expected Output: None
Explanation: No supporting version appears in the given list.
Input: ([], ["0.0.1"])
Expected Output: None
Explanation: Edge case: empty version list.
Input: (["007.000.009"], ["7.0.9"])
Expected Output: "7.0.9"
Explanation: Single valid version, with zero padding.
Solution
def solution(versions, supported_versions):
def parse(version):
if not isinstance(version, str):
return None
parts = version.split('.')
if len(parts) != 3:
return None
nums = []
for part in parts:
if part == '' or not part.isdigit():
return None
nums.append(int(part))
return tuple(nums)
supported_set = set()
if supported_versions is not None:
for v in supported_versions:
parsed = parse(v)
if parsed is not None:
supported_set.add(parsed)
best = None
if versions is not None:
for v in versions:
parsed = parse(v)
if parsed is None:
continue
if parsed in supported_set:
if best is None or parsed < best:
best = parsed
if best is None:
return None
return f"{best[0]}.{best[1]}.{best[2]}"
Time complexity: O(V + S), where V = len(versions) and S = len(supported_versions). Space complexity: O(S).
Hints
- Parse every valid version into a tuple of three integers so numeric comparison becomes easy.
- Because support is not monotonic, scanning the list and tracking the minimum supporting version is enough; binary search does not apply.
Part 2: Earliest Supporting Version with Sublinear Support Checks
Constraints
- 0 <= len(versions) <= 200000
- 0 <= len(supported_versions) <= 200000
- A valid version has exactly 3 non-empty numeric segments separated by dots
- Leading zeros are allowed and do not affect ordering
- The monotone representative guarantees listed above hold for all valid test cases
- Invalid version strings must be ignored
Examples
Input: (["1.0.0", "1.0.1", "2.0.0", "2.0.1", "2.1.0", "2.1.1", "3.0.0", "3.0.1"], ["2.0.1", "2.1.1", "3.0.1"])
Expected Output: "2.0.1"
Explanation: The first supporting major is 2, the first supporting minor inside major 2 is 0, and the first supporting patch there is 1.
Input: (["1.0.0", "1.0.1", "1.0.2", "1.1.0", "1.1.1", "2.0.0", "2.0.1"], ["1.0.1", "1.0.2", "1.1.1", "2.0.1"])
Expected Output: "1.0.1"
Explanation: Major 1 already supports the feature. Inside major 1, minor 0 supports before minor 1, and within minor 0 the first supporting patch is 1.
Input: (["1.0.0", "1.0.1", "2.0.0"], [])
Expected Output: None
Explanation: Edge case: no supporting version exists.
Input: (["007.000.009"], ["7.0.9"])
Expected Output: "7.0.9"
Explanation: Edge case: single valid version.
Input: (["bad", "01.002.000", "1.2.1", "1.3.0", "1.3.1", "2.0.0", "2.0.1", "1.2.0", "1.2.1"], ["001.002.001", "1.3.1", "2.0.1"])
Expected Output: "1.2.1"
Explanation: Invalid entries and duplicates are ignored. The earliest supporting version is `1.2.1`.
Solution
def solution(versions, supported_versions):
from collections import defaultdict
def parse(version):
if not isinstance(version, str):
return None
parts = version.split('.')
if len(parts) != 3:
return None
nums = []
for part in parts:
if part == '' or not part.isdigit():
return None
nums.append(int(part))
return tuple(nums)
def canonical(v):
return f"{v[0]}.{v[1]}.{v[2]}"
valid_versions = set()
if versions is not None:
for v in versions:
parsed = parse(v)
if parsed is not None:
valid_versions.add(parsed)
if not valid_versions:
return None
supported_set = set()
if supported_versions is not None:
for v in supported_versions:
parsed = parse(v)
if parsed is not None and parsed in valid_versions:
supported_set.add(parsed)
groups = defaultdict(lambda: defaultdict(list))
for major, minor, patch in valid_versions:
groups[major][minor].append(patch)
majors = sorted(groups.keys())
minor_list = {}
patch_list = {}
latest_minor = {}
latest_major = {}
for major in majors:
minors = sorted(groups[major].keys())
minor_list[major] = minors
for minor in minors:
patches = sorted(groups[major][minor])
patch_list[(major, minor)] = patches
latest_minor[(major, minor)] = (major, minor, patches[-1])
last_minor = minors[-1]
latest_major[major] = latest_minor[(major, last_minor)]
def first_true(values, predicate):
lo, hi = 0, len(values)
while lo < hi:
mid = (lo + hi) // 2
if predicate(values[mid]):
hi = mid
else:
lo = mid + 1
return lo
major_idx = first_true(majors, lambda major: latest_major[major] in supported_set)
if major_idx == len(majors):
return None
chosen_major = majors[major_idx]
minors = minor_list[chosen_major]
minor_idx = first_true(minors, lambda minor: latest_minor[(chosen_major, minor)] in supported_set)
if minor_idx == len(minors):
return None
chosen_minor = minors[minor_idx]
patches = patch_list[(chosen_major, chosen_minor)]
patch_idx = first_true(patches, lambda patch: (chosen_major, chosen_minor, patch) in supported_set)
if patch_idx == len(patches):
return None
return canonical((chosen_major, chosen_minor, patches[patch_idx]))
Time complexity: O(N log N) total time for parsing, deduplication, and sorting; support checks are O(log A + log B + log C), where A is the number of majors, B is the number of minors in the chosen major, and C is the number of patches in the chosen minor. Space complexity: O(N).
Hints
- Precompute the latest version in each major and the latest version in each minor of a major.
- Run three lower-bound style binary searches: one over majors, one over minors in the chosen major, and one over patches in the chosen minor.