查找最窄区间的算法,其中 m 将覆盖一组数字

Algorithm to find the narrowest intervals, m of which will cover a set of numbers

本文关键字:覆盖 数字 一组 区间 算法 其中 查找      更新时间:2023-10-16

假设你有一个n个数字的列表。您可以选择 m 个整数(让我们调用整数 a)。对于每个整数 a,删除包含范围 [a - x, a + x] 中的每个数字,其中 x 是一个数字。可以清除列表的 x 的最小值是多少?

例如,如果您的号码列表是

1 3 8 10 18 20 25

而 m = 2,答案是 x = 5。

您可以选择两个整数 5 和 20。这将清除列表,因为它会删除 [5-5, 5+5] 和 [20-5, 20+5] 之间的每个数字。

我将如何解决这个问题?我认为解决方案可能与动态规划有关。我不想要蛮力方法解决方案。

代码会非常有用,最好是用Java或C++或C。

提示

假设你有列表

1 3 8 10 18 20 25

并希望找到如果 x 等于 2,需要多少组来覆盖集合。

您可以通过选择第一个整数为 1+x(1 是列表中的最小数字)来以贪婪的方式解决此问题。 这将涵盖 1+x+x=5 以下的所有元素。 然后只需重复此过程,直到覆盖所有数字。

所以在这种情况下,下一个未覆盖的数字是 8,所以我们选择 8+x=10 并覆盖第二组中所有不超过 10+x=12 的数字。

同样,第三组将

涵盖[18,24],第四组将涵盖[25,29]。

这个 x 的值需要 4 组。 这太多了,所以我们需要增加 x 并重试。

您可以使用平分来标识 x 的最小值,该值确实覆盖了 m 组中的所有数字。

递归解决方案:

首先,你需要一个估计,你可以分成m组,然后估计(x)必须是~(更大 - 低元素)/2*m。如果有更好的解决方案,则在所有组中,它的x都低于extimated(x)!和 您可以使用第一组进行检查,然后递归重复。问题正在减少,直到你只有一个组:最后一个,你知道你的新解决方案是否更好,如果有更好的,你可以用它来丢弃另一个更糟糕的解决方案。

private static int estimate(int[] n, int m, int begin, int end) {
    return (((n[end - 1] - n[begin]) / m) + 1 )/2;
}
private static int calculate(int[] n, int m, int begin, int end, int estimatedX){
    if (m == 1){
        return estimate(n, 1, begin, end);
    } else {
        int bestX = estimatedX;
        for (int i = begin + 1; i <= end + 1 - m; i++) {
            // It split the problem:
            int firstGroupX = estimate(n, 1, begin, i);
            if (firstGroupX < bestX){
                bestX = Math.min(bestX, Math.max(firstGroupX, calculate(n, m-1, i, end, bestX)));
            } else {
                i = end;
            }
        }
        return bestX;
    }
}
public static void main(String[] args) {
    int[] n = {1, 3, 8, 10, 18, 20, 25};
    int m = 2;
    Arrays.sort(n);
    System.out.println(calculate(n, m, 0, n.length, estimate(n, m, 0, n.length)));
}

编辑:

长数版本: 主要思想, 它搜索距离的"岛屿"并将问题拆分为不同的岛屿。 就像分而治之一样, 它将"m"分布成岛屿。

private static long estimate(long[] n, long m, int begin, int end) {
    return (((n[end - 1] - n[begin]) / m) + 1) / 2;
}
private static long calculate(long[] n, long m, int begin, int end, long estimatedX) {
    if (m == 1) {
        return estimate(n, 1, begin, end);
    } else {
        long bestX = estimatedX;
        for (int i = begin + 1; i <= end + 1 - m; i++) {
            long firstGroupX = estimate(n, 1, begin, i);
            if (firstGroupX < bestX) {
                bestX = Math.min(bestX, Math.max(firstGroupX, calculate(n, m - 1, i, end, bestX)));
            } else {
                i = end;
            }
        }
        return bestX;
    }
}
private static long solver(long[] n, long m,  int begin, int end) {
    long estimate = estimate(n, m, begin, end);
    PriorityQueue<long[]> islands = new PriorityQueue<>((p0, p1) -> Long.compare(p1[0], p0[0]));
    int islandBegin = begin;
    for (int i = islandBegin; i < end -1; i++) {
        if (n[i + 1] - n[i] > estimate) {
            long estimatedIsland = estimate(n, 1, islandBegin, i+1);
            islands.add(new long[]{estimatedIsland, islandBegin, i, 1});
            islandBegin = i+1;
        }
    }
    long estimatedIsland = estimate(n, 1, islandBegin, end);
    islands.add(new long[]{estimatedIsland, islandBegin, end, 1});
    long result;
    if (islands.isEmpty() || m < islands.size()) {
        result = calculate(n, m, begin, end, estimate);
    } else {    
        long mFree = m - islands.size();
        while (mFree > 0) {
            long[] island = islands.poll();
            island[3]++;
            island[0] = solver(n, island[3], (int) island[1], (int) island[2]);
            islands.add(island);
            mFree--;
        }
        result = islands.poll()[0];
    }
    return result;
}
public static void main(String[] args) {
    long[] n = new long[63];
    for (int i = 1; i < n.length; i++) {
        n[i] = 2*n[i-1]+1;
    }
    long m = 32;
    Arrays.sort(n);
    System.out.println(solver(n, m, 0, n.length));
}

一个有效的算法可以是(假设列表被排序)->

  1. 我们可以将列表视为"m"整数组。

  2. 现在,对于每个组,计算"last_element - first_element+1",并将该值的最大值存储在变量中,例如"answers"。

  3. 现在"x"的值是"answers/2"。

我希望它很清楚这个算法是如何工作的。

我认为这同样是集群问题。例如,您可以使用 k 均值聚类算法:在 m 类上执行初始列表的分区,对于 x 获得最大大小除以获得的类中的两个。

1)你应该研究算法的时间和空间复杂度的最佳情况,平均情况和最坏情况的复杂性。

2)我认为大卫·佩雷斯·卡布雷拉的想法是正确的。 让我们假设平均情况(如下面的伪代码)

3)让整数列表用l表示

    keepGoing = true
    min_x = ceiling(l[size-1]-l[0])/(2m)
    while(keepGoing)
    {
        l2 = l.copy
        min_x = min_x-1
        mcounter = 1
        while(mcounter <= m)
        {
            firstElement = l2[0]
//  This while condition will likely result in an ArrayOutOfBoundsException
//  It's easy to fix this.
            while(l2[0] <= firstElement+2*min_x)
            {   remove(l2[0])   }
            mcounter = mcounter+1
        }
        if(l2.size>0)
            keepGoing = false
    }
    return min_x+1

4)考虑

l = {1, 2, 3, 4, 5, 6, 7}, m=2 (gives x=2)
l = {1, 10, 100, 1000, 10000, 100000, 1000000}, m=2
l = {1, 10, 100, 1000, 10000, 100000, 1000000}, m=3