Maximize Score of Numbers in Ranges
MedPrompt
You are given an array of integers start and an integer d, representing n intervals [start[i], start[i] + d].
You are asked to choose n integers where the ith integer must belong to the ith interval. The score of the chosen integers is defined as the minimum absolute difference between any two integers that have been chosen.
Return the maximum possible score of the chosen integers.
Example 1:
Input: start = [6,0,3], d = 2
Output: 4
Explanation:
The maximum possible score can be obtained by choosing integers: 8, 0, and 4. The score of these chosen integers is min(|8 - 0|, |8 - 4|, |0 - 4|) which equals 4.
Example 2:
Input: start = [2,6,13,13], d = 5
Output: 5
Explanation:
The maximum possible score can be obtained by choosing integers: 2, 7, 13, and 18. The score of these chosen integers is min(|2 - 7|, |2 - 13|, |2 - 18|, |7 - 13|, |7 - 18|, |13 - 18|) which equals 5.
Constraints:
2 <= start.length <= 1050 <= start[i] <= 1090 <= d <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
This approach tackles the problem by exploring every possible ordering of intervals. For a given potential score k, it checks if there exists any permutation of the n intervals that allows for choosing n numbers with a minimum difference of k. This check is then used within a binary search framework to find the maximum possible score.
Algorithm
- The problem of finding the maximum score can be solved by binary searching for the score
k. - To check if a score
kis achievable (check(k)), we must determine if there's an ordering of intervals that allows for a valid selection of numbers. - A brute-force method for
check(k)is to try alln!permutations of the intervals. - For each permutation
p = (p_0, p_1, ..., p_{n-1}), greedily select the smallest possible numbersc_{p_i}that satisfy the constraintsc_{p_i} >= c_{p_{i-1}} + kandc_{p_i}is in its interval. - If any permutation allows for a valid selection,
check(k)returns true. - If all
n!permutations fail,check(k)returns false.
Walkthrough
The core of this method is to solve the decision problem: "Is a score of k achievable?" A brute-force way to answer this is to consider every possible sequence in which we can pick numbers from the intervals. There are n! such sequences (permutations).
For each permutation of the intervals, we can greedily try to assign the smallest possible numbers. We iterate through the permuted intervals, and for each one, we choose the smallest valid number that is at least k greater than the number chosen for the previous interval in the permutation. If we can successfully assign numbers for an entire permutation, then the score k is achievable.
This entire checking process is wrapped in a binary search over the possible range of scores to find the maximum k for which the check returns true.
import java.util.Arrays;import java.util.Collections;import java.util.ArrayList;import java.util.List; class Solution { // This approach is too slow and will time out, presented for theoretical purposes. public int maximizeScore(int[] start, int d) { int n = start.length; List<Integer> p = new ArrayList<>(); for (int i = 0; i < n; i++) { p.add(i); } long low = 0, high = 2_000_000_000L, ans = 0; while (low <= high) { long mid = low + (high - low) / 2; if (check(mid, start, d, n)) { ans = mid; low = mid + 1; } else { high = mid - 1; } } return (int) ans; } private boolean check(long k, int[] start, int d, int n) { List<Integer> p = new ArrayList<>(); for (int i = 0; i < n; i++) { p.add(i); } return permutations(p, 0, start, d, k); } private boolean permutations(List<Integer> p, int startIdx, int[] start, int d, long k) { if (startIdx == p.size()) { return checkPermutation(p, start, d, k); } for (int i = startIdx; i < p.size(); i++) { Collections.swap(p, i, startIdx); if (permutations(p, startIdx + 1, start, d, k)) { return true; } Collections.swap(p, i, startIdx); // backtrack } return false; } private boolean checkPermutation(List<Integer> p, int[] start, int d, long k) { long lastVal = -2_000_000_000_000_000_000L; // A very small number for (int index : p) { long s = start[index]; long e = s + d; long currentVal = Math.max(s, lastVal + k); if (currentVal > e) { return false; } lastVal = currentVal; } return true; }}Complexity
Time
O(N! * N * log R), where `N` is the number of intervals and `R` is the search range for the score. This is computationally infeasible.
Space
O(N) for storing the permutation and recursion stack.
Trade-offs
Pros
Conceptually simple as it directly models the problem of trying all orderings.
Cons
Extremely inefficient due to the factorial complexity.
Only feasible for very small
n(e.g., n <= 10), which is far below the problem constraints.
This efficient approach combines binary search on the answer with a greedy strategy. The problem is reframed as finding the largest k for which we can answer the question: "Is it possible to achieve a score of at least k?". This decision problem can be solved efficiently by sorting the intervals by their start times and greedily choosing the smallest possible valid number for each interval in that order.
Algorithm
- The problem of maximizing a minimum value is a classic pattern for binary search on the answer. We binary search for the score
k. - To efficiently check if a score
kis achievable (can_achieve(k)), we use a greedy strategy. - Sort the intervals based on their start points. This is the key insight.
- Iterate through the sorted intervals. For each interval, greedily pick the smallest possible number that is valid.
- The chosen number for the current interval
[s, e]must be at leastkgreater than the number chosen for the previous interval. So, the candidate choice ismax(s, last_chosen_value + k). - If this candidate choice is greater than
e, it's impossible to achieve scorek. Returnfalse. - If we can find a valid number for all intervals, return
true. - The initial sort is done once, making each
can_achieve(k)check anO(N)operation.
Walkthrough
The optimal strategy involves binary searching for the maximum possible score. Let's say we want to check if a score k is achievable. This subproblem can be solved greedily.
The crucial insight is to process the intervals in a specific order. If we sort the intervals based on their start points, we can make a greedy choice at each step. We iterate through the sorted intervals and for each interval [start[i], start[i] + d], we must select a number c_i.
To maximize our chances of success for subsequent intervals, we should choose c_i to be as small as possible. The chosen number c_i must be at least k greater than the number chosen for the previous interval in the sorted list (last_chosen_value). It must also be within its own interval, so c_i >= start[i]. Combining these, the smallest possible value for c_i is max(start[i], last_chosen_value + k).
We then check if this smallest possible choice is valid, i.e., if it's less than or equal to start[i] + d. If this condition fails for any interval, it's impossible to achieve a score of k, and the check fails. If we successfully find a number for every interval, the score k is achievable.
The overall algorithm first sorts the intervals (as pairs of start and end points), then performs a binary search on the score k, using this greedy O(N) check at each step.
import java.util.Arrays; class Solution { class Interval { long start, end; Interval(long start, long end) { this.start = start; this.end = end; } } public int maximizeScore(int[] start, int d) { int n = start.length; Interval[] intervals = new Interval[n]; for (int i = 0; i < n; i++) { intervals[i] = new Interval(start[i], (long)start[i] + d); } Arrays.sort(intervals, (a, b) -> Long.compare(a.start, b.start)); long low = 0, high = 2_000_000_000L, ans = 0; while (low <= high) { long mid = low + (high - low) / 2; if (canAchieve(mid, intervals, n)) { ans = mid; low = mid + 1; } else { high = mid - 1; } } return (int) ans; } private boolean canAchieve(long k, Interval[] intervals, int n) { long lastChosenValue = -3_000_000_000L; // A very small number, smaller than any possible choice for (int i = 0; i < n; i++) { long currentChoice = Math.max(intervals[i].start, lastChosenValue + k); if (currentChoice > intervals[i].end) { return false; } lastChosenValue = currentChoice; } return true; }}Complexity
Time
O(N log N + N log R), where `N` is the number of intervals and `R` is the search range for the score. The `O(N log N)` term comes from the initial sort, and `O(N log R)` from the binary search where each check takes `O(N)`.
Space
O(N) to store the intervals for sorting.
Trade-offs
Pros
Highly efficient and passes within time limits.
The greedy strategy is proven to be optimal for the check function.
The binary search approach is standard for this type of
max-minproblem.
Cons
Requires careful handling of large numbers (using
long) to prevent overflow.
Solutions
Solution
class Solution {private int[] start;private int d;public int maxPossibleScore(int[] start, int d) { Arrays.sort(start); this.start = start; this.d = d; int n = start.length; int l = 0, r = start[n - 1] + d - start[0]; while (l < r) { int mid = (l + r + 1) >>> 1; if (check(mid)) { l = mid; } else { r = mid - 1; } } return l; }private boolean check(int mi) { long last = Long.MIN_VALUE; for (int st : start) { if (last + mi > st + d) { return false; } last = Math.max(st, last + mi); } return true; }}class Solution {public: int maxPossibleScore(vector<int> &start, int d) { ranges ::sort(start); auto check = [&](int mi) -> bool { long long last = LLONG_MIN; for (int st : start) { if (last + mi > st + d) { return false; } last = max((long long)st, last + mi); } return true; }; int l = 0, r = start.back() + d - start[0]; while (l < r) { int mid = l + (r - l + 1) / 2; if (check(mid)) { l = mid; } else { r = mid - 1; } } return l; }};class Solution: def maxPossibleScore(self, start: List[int], d: int) -> int: def check(mi: int) -> bool: last = - inf for st in start: if last + mi > st + d: return False last = max(st, last + mi) return True start . sort() l, r = 0, start[- 1] + d - start[0] while l < r: mid = (l + r + 1) >> 1 if check(mid): l = mid else: r = mid - 1 return lVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.