LeetCode 3281 - Maximize Score of Numbers in Ranges
The problem presents a list of intervals derived from the start array and a fixed d. Specifically, each interval is [start[i], start[i] + d].
Difficulty: 🟡 Medium
Topics: Array, Binary Search, Greedy, Sorting
Solution
Problem Understanding
The problem presents a list of intervals derived from the start array and a fixed d. Specifically, each interval is [start[i], start[i] + d]. The goal is to select exactly one integer from each interval such that the minimum absolute difference between any two chosen integers is maximized. In other words, you want to spread the chosen numbers as far apart as possible, while staying within their respective intervals.
The input start represents the starting points of intervals and d is the fixed interval length. The output is a single integer representing the largest possible minimum difference among chosen numbers.
Constraints indicate that start can be very large (up to 10^5) and the numbers themselves can be very large (up to 10^9). This rules out any brute-force approach that considers all possible integer selections, because the search space is exponential. Edge cases include intervals of length zero (where d = 0) or multiple identical intervals (which can reduce the maximum possible score).
Approaches
A brute-force approach would try every combination of integers from each interval and compute the minimum absolute difference for each combination. While this would guarantee correctness, it is clearly infeasible because there are potentially (d+1)^n combinations, which is enormous given n can reach 10^5.
The key insight for an optimal solution is to realize that the problem can be treated as a maximum minimum distance problem, commonly solved using a binary search on the answer. Specifically, you can binary search on the possible minimum difference score and, for each candidate value, attempt to greedily assign numbers from left to right to satisfy that minimum distance. Sorting intervals by their start point ensures that this greedy approach will always try the earliest possible number in each interval, leaving maximum room for subsequent numbers.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O((d+1)^n) | O(n) | Try every combination of integer selections from intervals. |
| Optimal | O(n log(range)) | O(n) | Binary search on minimum difference + greedy placement. range is the numeric span of the intervals. |
Algorithm Walkthrough
-
Sort the intervals based on the start values. This ensures we can process intervals from left to right.
-
Define the search range for binary search. The minimum possible score is
0and the maximum possible score ismax(start) + d - min(start), the full range spanned by all intervals. -
Binary search on
score(the candidate minimum absolute difference): -
Initialize
low = 0andhigh = max_range. -
While
low <= high, computemid = (low + high) // 2as the candidate score. -
Greedy placement check: Start with the first interval, choose the smallest number in that interval, and for each subsequent interval, choose the earliest number that is at least
midaway from the previous number. If this is impossible for any interval, the candidatemidis too high. -
If placement succeeds, move
low = mid + 1(we can try a higher score); otherwise, movehigh = mid - 1. -
Return
highas the largest score that passed the greedy check.
Why it works: Sorting ensures that the greedy choice of the earliest valid number is optimal because it leaves maximum room for future intervals. Binary search guarantees that the largest feasible minimum difference is found efficiently.
Python Solution
from typing import List
class Solution:
def maxPossibleScore(self, start: List[int], d: int) -> int:
intervals = sorted((s, s + d) for s in start)
def can_achieve(score: int) -> bool:
prev = intervals[0][0]
for l, r in intervals[1:]:
candidate = max(prev + score, l)
if candidate > r:
return False
prev = candidate
return True
low, high = 0, max(r for _, r in intervals) - min(l for l, _ in intervals)
result = 0
while low <= high:
mid = (low + high) // 2
if can_achieve(mid):
result = mid
low = mid + 1
else:
high = mid - 1
return result
The code first converts start into intervals and sorts them. The helper can_achieve checks whether a given minimum score is feasible by greedily choosing numbers. Binary search then efficiently finds the maximum feasible score.
Go Solution
func maxPossibleScore(start []int, d int) int {
n := len(start)
intervals := make([][2]int, n)
for i, s := range start {
intervals[i] = [2]int{s, s + d}
}
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
canAchieve := func(score int) bool {
prev := intervals[0][0]
for i := 1; i < n; i++ {
candidate := max(prev + score, intervals[i][0])
if candidate > intervals[i][1] {
return false
}
prev = candidate
}
return true
}
low, high := 0, intervals[n-1][1] - intervals[0][0]
result := 0
for low <= high {
mid := (low + high) / 2
if canAchieve(mid) {
result = mid
low = mid + 1
} else {
high = mid - 1
}
}
return result
}
func max(a, b int) int {
if a > b { return a }
return b
}
In Go, the approach is identical. Arrays are used for intervals, and sort.Slice handles sorting. canAchieve performs greedy placement, and binary search finds the maximum feasible score.
Worked Examples
Example 1: start = [6,0,3], d = 2
Intervals: [6,8], [0,2], [3,5] sorted → [0,2], [3,5], [6,8].
Binary search checks candidate score = 4:
- Pick 0 from [0,2]
- Next interval [3,5], earliest ≥ 0 + 4 → candidate 4 → fits in [3,5] → pick 4
- Next interval [6,8], earliest ≥ 4 + 4 → candidate 8 → fits in [6,8] → pick 8
Minimum difference among chosen = 4, feasible.
Example 2: start = [2,6,13,13], d = 5
Intervals: [2,7], [6,11], [13,18], [13,18]
Candidate score = 5:
- Pick 2 from [2,7]
- Next [6,11], earliest ≥ 2 + 5 → 7 → pick 7
- Next [13,18], earliest ≥ 7 + 5 → 12 → fits [13,18]? Pick 13 (≥12)
- Next [13,18], earliest ≥ 13 + 5 → 18 → fits [13,18]? Pick 18
Minimum difference = 5, feasible.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log R) | n intervals, binary search on range R = max(start) + d - min(start) and greedy check O(n) each. |
| Space | O(n) | Storing intervals and temporary variables. |
The binary search over the numeric range is efficient because the interval range can be up to 10^9, so log(R) ≈ 30, which is negligible compared to n ≤ 10^5.
Test Cases
# Provided examples
assert Solution().maxPossibleScore([6,0,3], 2) == 4 # Example 1
assert Solution().maxPossibleScore([2,6,13,13], 5) == 5 # Example 2
# Edge cases
assert Solution().maxPossibleScore([0,0], 0) == 0 # intervals of length 0
assert Solution().maxPossibleScore([0,1,2], 0) == 1 # minimal spread
assert Solution().maxPossibleScore([0,1000000000], 0) == 1000000000 # max span
# Larger input
assert Solution().maxPossibleScore([i*10 for i in range(1000)], 5) == 10 # uniform spacing
| Test | Why |
|---|---|
[6,0,3], 2 |
Basic example, small numbers |
[2,6,13,13], 5 |
Multiple intervals with overlap |
[0,0], 0 |
Interval length 0 |
[0,1,2], 0 |
Minimal spacing |
[0,10^9],0 |
Max range edge case |
| `[i*10 for i in range(1000)],5 |