1. 问题背景与需求分析
最近在准备华为OD机考C卷时遇到一道非常有意思的算法题,题目是关于在沙漠化地区进行植树造林的最佳间距计算。这道题看似简单,但实际考察了我们对二分查找算法的深入理解和灵活应用能力。
题目要求我们在给定的可种植坐标点上选择若干个位置进行植树,目标是让所有相邻树苗之间的最小间距尽可能大。这在实际工程中很有意义——在树苗有限的情况下,合理的间距分布能够最大化防沙效果。
2. 问题建模与算法选择
2.1 问题形式化描述
给定:
- N个可种植点的坐标(无序)
- 需要种植的树苗数量K
要求:
找出一种种植方案,使得所有相邻树苗之间的最小间距最大化。
2.2 算法选择思路
这个问题属于典型的"最大化最小值"问题,这类问题通常可以通过二分查找来解决。原因在于:
- 解空间有序:最小间距的可能取值范围在0到最大坐标差之间,是有序的
- 验证可行性:对于给定的间距d,我们可以在O(N)时间内验证是否存在满足条件的种植方案
- 时间复杂度:二分查找可以将时间复杂度从暴力解的O(N^2)降低到O(N log D),其中D是坐标范围
3. 详细解题步骤
3.1 输入处理与预处理
首先我们需要对输入数据进行处理:
python复制n = int(input()) # 可种植点数量
points = list(map(int, input().split())) # 可种植点坐标
k = int(input()) # 需要种植的树苗数量
# 对坐标进行排序
points.sort()
注意:输入坐标可能是无序的,必须先排序才能进行后续计算。这是解题的关键第一步。
3.2 二分查找框架设计
我们使用二分查找来确定最大可能的最小间距:
python复制def max_min_distance(points, k):
left = 0
right = points[-1] - points[0]
result = 0
while left <= right:
mid = (left + right) // 2
if can_place(points, k, mid):
result = mid
left = mid + 1
else:
right = mid - 1
return result
3.3 可行性验证函数
这是算法的核心部分,判断在给定最小间距d的情况下,能否种植k棵树苗:
python复制def can_place(points, k, d):
count = 1
last = points[0]
for i in range(1, len(points)):
if points[i] - last >= d:
count += 1
last = points[i]
if count == k:
return True
return count >= k
这个函数的工作原理:
- 从第一个点开始种植
- 遍历后续点,只有当前点与上一个种植点的距离≥d时才种植
- 统计能够种植的树苗数量
3.4 完整代码实现
以下是Python的完整实现:
python复制def max_min_distance(points, k):
points.sort()
left = 0
right = points[-1] - points[0]
result = 0
def can_place(d):
count = 1
last = points[0]
for i in range(1, len(points)):
if points[i] - last >= d:
count += 1
last = points[i]
if count == k:
return True
return count >= k
while left <= right:
mid = (left + right) // 2
if can_place(mid):
result = mid
left = mid + 1
else:
right = mid - 1
return result
# 读取输入
n = int(input())
points = list(map(int, input().split()))
k = int(input())
# 计算并输出结果
print(max_min_distance(points, k))
4. 算法复杂度分析
-
时间复杂度:O(N log D)
- 排序:O(N log N)
- 二分查找:O(log D),D是坐标范围
- 每次验证:O(N)
-
空间复杂度:O(1)(除了存储输入数据外,只使用了常数空间)
5. 边界条件与测试用例
5.1 常见边界情况
-
树苗数量等于可种植点数量:
- 输入:
[1,2,3], k=3 - 输出:最小间距为1(相邻点间距)
- 输入:
-
只有两个树苗:
- 输入:
[1,5,10], k=2 - 输出:最大最小间距为9(选择1和10)
- 输入:
-
坐标范围很大:
- 需要确保算法在1e7的范围内也能高效运行
5.2 测试用例验证
python复制# 测试用例1:题目示例
points = [1,3,5,6,7,10,13]
k = 3
assert max_min_distance(points, k) == 6
# 测试用例2:所有点都种植
points = [1,2,3,4,5]
k = 5
assert max_min_distance(points, k) == 1
# 测试用例3:最大可能间距
points = [1,100,1000,10000]
k = 2
assert max_min_distance(points, k) == 9999
6. 不同语言实现要点
6.1 Java实现
java复制import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] points = new int[n];
for (int i = 0; i < n; i++) {
points[i] = sc.nextInt();
}
int k = sc.nextInt();
Arrays.sort(points);
System.out.println(maxMinDistance(points, k));
}
private static int maxMinDistance(int[] points, int k) {
int left = 0;
int right = points[points.length-1] - points[0];
int result = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
if (canPlace(points, k, mid)) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
private static boolean canPlace(int[] points, int k, int d) {
int count = 1;
int last = points[0];
for (int i = 1; i < points.length; i++) {
if (points[i] - last >= d) {
count++;
last = points[i];
if (count == k) {
return true;
}
}
}
return count >= k;
}
}
6.2 C++实现
cpp复制#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool canPlace(const vector<int>& points, int k, int d) {
int count = 1;
int last = points[0];
for (int i = 1; i < points.size(); ++i) {
if (points[i] - last >= d) {
++count;
last = points[i];
if (count == k) {
return true;
}
}
}
return count >= k;
}
int maxMinDistance(vector<int>& points, int k) {
sort(points.begin(), points.end());
int left = 0;
int right = points.back() - points.front();
int result = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
if (canPlace(points, k, mid)) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
int main() {
int n;
cin >> n;
vector<int> points(n);
for (int i = 0; i < n; ++i) {
cin >> points[i];
}
int k;
cin >> k;
cout << maxMinDistance(points, k) << endl;
return 0;
}
6.3 JavaScript实现
javascript复制function maxMinDistance(points, k) {
points.sort((a, b) => a - b);
let left = 0;
let right = points[points.length - 1] - points[0];
let result = 0;
function canPlace(d) {
let count = 1;
let last = points[0];
for (let i = 1; i < points.length; i++) {
if (points[i] - last >= d) {
count++;
last = points[i];
if (count === k) {
return true;
}
}
}
return count >= k;
}
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (canPlace(mid)) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
// 读取输入
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let input = [];
rl.on('line', (line) => {
input.push(line);
}).on('close', () => {
const n = parseInt(input[0]);
const points = input[1].split(' ').map(Number);
const k = parseInt(input[2]);
console.log(maxMinDistance(points, k));
});
7. 常见错误与调试技巧
7.1 典型错误
-
未排序输入数据:
- 错误现象:得到的结果比预期小
- 解决方法:确保在处理前先对坐标进行排序
-
二分查找边界错误:
- 初始right值设置过小
- 循环条件错误(使用<而不是<=)
-
可行性验证逻辑错误:
- 计数初始值应为1(第一个点已种植)
- 比较时使用>=而不是>
7.2 调试技巧
-
打印中间结果:
python复制print(f"Trying distance {mid}, can place: {can_place(mid)}") -
小规模测试:
- 使用简单的手算可验证的测试用例
-
边界测试:
- 测试k=2和k=n的情况
8. 算法优化与变种
8.1 优化思路
-
提前终止:
- 在canPlace函数中,一旦count达到k即可提前返回
-
二分查找优化:
- 使用更精细的边界更新策略
8.2 相关问题变种
-
最小化最大间距:
- 类似的问题,但目标是相反的
-
多维空间种植:
- 将问题扩展到二维或三维空间
-
带权重的种植点:
- 每个种植点有不同的效益值,需要综合考虑
在实际开发中,我发现这类最大化最小值的问题在资源分配、任务调度等场景中经常出现。掌握这种二分查找的应用模式,可以解决许多类似的问题。特别是在处理大规模数据时,二分查找的O(log N)特性显得尤为重要。