AI刷题之组队问题 | 豆包MarsCode AI刷题

205 阅读4分钟

“组队问题”题目要求

一、问题描述

小明的公司要进行集体活动,需要将公司内的 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代码思路

  1. 搜索范围确定
    最大可能的差别值是所有点对之间差别值的最大值。该值用于设置二分查找的初始上限。

  2. 二分查找
    利用二分查找确定可能的 LLL。对于每个候选 LLL,检查是否可以分成至少 kkk 个组。

  3. 组划分判定(并查集)
    使用并查集维护分组信息,确保每组内的点之间的差别值均小于等于 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);
    }
}

四、知识总结

在算法竞赛与工程实际中,分组问题是一种常见且具有挑战性的任务,尤其是涉及约束条件的分组。本篇以一道分组问题为例,结合 并查集二分查找 的融合,解析其实现过程及设计思路,帮助读者掌握解决类似问题的通用方法。

  1. 二分查找的应用
    二分查找不仅限于排序数组,还可以用于搜索满足特定条件的最优值。

  2. 并查集的用途
    并查集适用于动态连通性问题,在组划分、网络连接等场景中非常高效。

通过本算法,我们以 n 较小时的性能为代价,换取了较为灵活的分组能力。