“组队问题”题目要求
一、问题描述
小明的公司要进行集体活动,需要将公司内的 nn 个人分组,每个人只能属于一个组。每个人有两个属性:能力值 aiai 和性格值 bibi。两个人的差别值定义为能力值的差与性格值的差之和,即 ∣ai−aj∣+∣bi−bj∣∣ai−aj∣+∣bi−bj∣。
分组时要求有至少 kk 个非空小组,并且如果两个人的差别值不超过某个差别上限 LL,那么他们必须在同一个组内。现在我们需要确定在满足至少 kk 个组的前提下,差别上限 LL 的最大值是多少。
例如:给定 33 个人,能力值分别为 [1, 9, 3],性格值分别为 [2, 7, 8],我们可以将第 22 人和第 33 人分在一组,第一人单独一组,在这种情况下差别上限 LL 的最大值为 77。
二、测试样例
样例1:
输入:
n = 3 ,k = 2,a = [1, 9, 3],b = [2, 7, 8]
输出:7
样例2:
输入:
n = 4 ,k = 3,a = [10, 20, 30, 40],b = [5, 15, 25, 35]
输出:19
样例3:
输入:
n = 5 ,k = 2,a = [100, 50, 25, 75, 10],b = [90, 45, 20, 80, 15]
输出:59
三、题目解析
3.1代码思路
-
搜索范围确定:
最大可能的差别值是所有点对之间差别值的最大值。该值用于设置二分查找的初始上限。 -
二分查找:
利用二分查找确定可能的 LLL。对于每个候选 LLL,检查是否可以分成至少 kkk 个组。 -
组划分判定(并查集) :
使用并查集维护分组信息,确保每组内的点之间的差别值均小于等于 LLL。
3.2详细代码
public class Main {
public static int solution(int n, int k, int[] a, int[] b) {
// 计算所有可能的差别值的最大值
int maxDiff = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
maxDiff = Math.max(maxDiff, Math.abs(a[i] - a[j]) + Math.abs(b[i] - b[j]));
}
}
// 二分查找最大差别上限 L
int left = 0, right = maxDiff;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (canFormGroups(n, k, a, b, mid)) {
left = mid;
} else {
right = mid - 1;
}
}
return left;
}
// 判断在给定的差别上限 L 下是否可以分成至少 k 个组
private static boolean canFormGroups(int n, int k, int[] a, int[] b, int L) {
// 使用并查集来维护分组信息
UnionFind uf = new UnionFind(n);
// 遍历所有可能的配对,如果差别值不超过 L,则将它们合并
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (Math.abs(a[i] - a[j]) + Math.abs(b[i] - b[j]) <= L) {
uf.union(i, j);
}
}
}
// 计算并查集中有多少个不同的组
int groupCount = uf.count();
// 判断是否至少有 k 个组
return groupCount >= k;
}
// 并查集类
static class UnionFind {
int[] parent;
int[] rank;
int count;
UnionFind(int n) {
parent = new int[n];
rank = new int[n];
count = n;
for (int i = 0; i < n; i++) {
parent[i] = i;
rank[i] = 1;
}
}
int find(int p) {
// 路径压缩
while (p != parent[p]) {
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}
void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP != rootQ) {
// 按秩合并
if (rank[rootP] > rank[rootQ]) {
parent[rootQ] = rootP;
} else if (rank[rootP] < rank[rootQ]) {
parent[rootP] = rootQ;
} else {
parent[rootQ] = rootP;
rank[rootP]++;
}
count--;
}
}
int count() {
return count;
}
}
public static void main(String[] args) {
System.out.println(solution(3, 2, new int[]{1, 9, 3}, new int[]{2, 7, 8}) == 7);
System.out.println(solution(4, 3, new int[]{10, 20, 30, 40}, new int[]{5, 15, 25, 35}) == 19);
System.out.println(solution(5, 2, new int[]{100, 50, 25, 75, 10}, new int[]{90, 45, 20, 80, 15}) == 59);
}
}
四、知识总结
在算法竞赛与工程实际中,分组问题是一种常见且具有挑战性的任务,尤其是涉及约束条件的分组。本篇以一道分组问题为例,结合 并查集 与 二分查找 的融合,解析其实现过程及设计思路,帮助读者掌握解决类似问题的通用方法。
-
二分查找的应用:
二分查找不仅限于排序数组,还可以用于搜索满足特定条件的最优值。 -
并查集的用途:
并查集适用于动态连通性问题,在组划分、网络连接等场景中非常高效。
通过本算法,我们以 n 较小时的性能为代价,换取了较为灵活的分组能力。