Medium
题目描述
给你一个整数数组 start 和一个整数 d,表示 n 个区间 [start[i], start[i] + d]。
你需要选择 n 个整数,其中第 i 个整数必须属于第 i 个区间。所选整数的得分定义为任意两个所选整数之间的最小绝对差。
返回所选整数的最大可能得分。
示例 1:
输入:start = [6,0,3], d = 2
输出:4
解释:
可以通过选择整数 8, 0, 4 来获得最大可能得分。
这些所选整数的得分是 min(|8 - 0|, |8 - 4|, |0 - 4|) = 4。
示例 2:
输入:start = [2,6,13,13], d = 5
输出:5
解释:
可以通过选择整数 2, 7, 13, 18 来获得最大可能得分。
这些所选整数的得分是 min(|2 - 7|, |2 - 13|, |2 - 18|, |7 - 13|, |7 - 18|, |13 - 18|) = 5。
约束条件:
2 <= start.length <= 10^50 <= start[i] <= 10^90 <= d <= 10^9
提示:
- 这里可以使用二分查找吗?
- 假设答案是 x。我们可以通过排序 start 来找到一个有效的整数配置,第一个整数应该是 start[0],然后每个后续整数应该是 [start[i], start[i] + d] 中大于 last_chosen_value + x 的最小整数。
- 对 x 进行二分查找
解题思路
解题思路
这道题的核心思想是使用二分查找 + 贪心策略来解决。
首先分析问题:我们要在每个区间内选择一个数,使得所选数字之间的最小差值最大。这是一个典型的最大化最小值问题,可以用二分查找来解决。
核心观察:
- 如果我们固定最小差值为
x,那么可以贪心地验证是否能选出满足条件的数字 - 为了让相邻数字的差值尽可能大,我们应该对区间起点进行排序
- 贪心策略:选择第一个区间的起点,然后在后续每个区间中选择满足最小差值要求的最小数字
算法步骤:
- 对
start数组排序 - 二分查找答案范围:左边界为 0,右边界为区间总跨度
- 对于每个候选答案
mid,检查是否能在每个区间内选择数字使得最小差值至少为mid - 验证函数使用贪心策略:从第一个区间开始,每次选择满足条件的最小数字
贪心验证的正确性: 如果存在一个有效方案,那么贪心选择(每次选最小的满足条件的数)一定也能得到有效方案,因为选择更小的数不会影响后续选择的可行性。
时间复杂度:O(n log n + n log(max_range)),其中排序需要 O(n log n),二分查找需要 O(log(max_range)),每次验证需要 O(n)。
代码实现
class Solution {
public:
int maxPossibleScore(vector<int>& start, int d) {
sort(start.begin(), start.end());
long long left = 0, right = (long long)start.back() + d - start[0];
long long result = 0;
while (left <= right) {
long long mid = left + (right - left) / 2;
if (canAchieveScore(start, d, mid)) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
private:
bool canAchieveScore(vector<int>& start, int d, long long minDiff) {
long long lastChosen = start[0];
for (int i = 1; i < start.size(); i++) {
long long nextMin = lastChosen + minDiff;
long long intervalStart = start[i];
long long intervalEnd = (long long)start[i] + d;
if (nextMin > intervalEnd) {
return false;
}
lastChosen = max(nextMin, intervalStart);
}
return true;
}
};
class Solution:
def maxPossibleScore(self, start: List[int], d: int) -> int:
start.sort()
left, right = 0, start[-1] + d - start[0]
result = 0
while left <= right:
mid = (left + right) // 2
if self.canAchieveScore(start, d, mid):
result = mid
left = mid + 1
else:
right = mid - 1
return result
def canAchieveScore(self, start: List[int], d: int, minDiff: int) -> bool:
lastChosen = start[0]
for i in range(1, len(start)):
nextMin = lastChosen + minDiff
intervalStart = start[i]
intervalEnd = start[i] + d
if nextMin > intervalEnd:
return False
lastChosen = max(nextMin, intervalStart)
return True
public class Solution {
public int MaxPossibleScore(int[] start, int d) {
Array.Sort(start);
long left = 0, right = (long)start[start.Length - 1] + d - start[0];
long result = 0;
while (left <= right) {
long mid = left + (right - left) / 2;
if (CanAchieveScore(start, d, mid)) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return (int)result;
}
private bool CanAchieveScore(int[] start, int d, long minDiff) {
long lastChosen = start[0];
for (int i = 1; i < start.Length; i++) {
long nextMin = lastChosen + minDiff;
long intervalStart = start[i];
long intervalEnd = (long)start[i] + d;
if (nextMin > intervalEnd) {
return false;
}
lastChosen = Math.Max(nextMin, intervalStart);
}
return true;
}
}
var maxPossibleScore = function(start, d) {
start.sort((a, b) => a - b);
let left = 0, right = start[start.length - 1] + d - start[0];
let result = 0;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (canAchieveScore(start, d, mid)) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
};
function canAchieveScore(start, d, minDiff) {
let lastChosen = start[0];
for (let i = 1; i < start.length; i++) {
let nextMin = lastChosen + minDiff;
let intervalStart = start[i];
let intervalEnd = start[i] + d;
if (nextMin > intervalEnd) {
return false;
}
lastChosen = Math.max(nextMin, intervalStart);
}
return true;
}
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n + n log(max_range)) | 排序 O(n log n),二分查找 O(log(max_range)),每次验证 O(n) |
| 空间复杂度 | O(1) | 只使用常数额外空间,排序为原地排序 |