最大化阵列之间的最小距离
Maximize minimum distance between arrays
假设您有 n 个排序的数字数组,您需要从每个数组中选择 一个 个数字,以使 n 个所选元素之间的最小距离最大化。
示例:
arrays:
[0, 500]
[100, 350]
[200]
2<=n<=10
并且每个数组可以有 ~10^3-10^4
个元素。
在此示例中,最大化最小距离的最佳解决方案是选择数字:500、350、200 或 0、200、350,其中最小距离为 150,是每个组合的最大可能值。
我正在寻找一种算法来解决这个问题。我知道我可以对最大最小距离进行二分搜索,但我看不出如何确定是否存在最大最小距离至少为 d 的解决方案,以便二分搜索起作用。我在想也许动态编程会有所帮助,但还没有找到 dp 的解决方案。
当然,生成所有包含 n 个元素的组合效率不高。我已经尝试过回溯,但它很慢,因为它尝试了所有组合。
n ≤ 10 表明我们可以对 n 取指数依赖。这是
O(2n m n) 时间算法,其中 m 是
数组。
我想到的动态规划方法是,对于
数组,计算所有对(最大数量,最小距离)
有效边界,我们必须从每个边界中选择一个数字
子集中的数组。通过有效边界我的意思是如果我们有
两对 (a, b) ≠ (c, d) 且 a ≤ c 且 b ≥ d,则 (c, d) 不在
有效边界。我们希望对这些边界进行分类
快速合并。
具有空子集的基本情况很简单:有一对,(最小
距离 = ∞, 最大数 = −∞).
对于数组的每个非空子集,以某种顺序扩展
包含顺序,我们计算子集中每个数组的边界,
表示该数组贡献的解决方案的子集
最大数量。然后我们合并这些边界。 (天真地这让我们付出了代价
log n 的另一个因素,可能不值得避免麻烦
鉴于 n ≤ 10,但我们可以通过在
开始启用未来的合并以使用分桶。)
从数组的一个子集和另一个数组构造一个新的边界
还涉及合并。我们在开始时初始化一个迭代器
边界(即最小最大数)和开始处的迭代器
数组(即最少的数字)。虽然两个迭代器都没有结束,
- 发出一个候选对(min(最小距离,数组数-最大
数), 数组数).
- 如果最小值小于或等于最小距离,增加
边界迭代器。如果最小值小于或等于数组编号
− 最大数,递增数组迭代器。
剔除候选对以仅留下有效边界。有
一种在代码中执行此操作的优雅方法,但更难以解释。
让我们看一个具有最佳选择的示例,x
(水平数组 A、B、C、D):
A x
B b x b
C x c
D d x
我们基于范围的递归可以是:让f(low, excluded)
表示没有excluded
中元素的子集的两个选定元素(从数组1到n
)之间的最大最近距离,其中 low
是最低的选择元素。那么:
(1)
f(low, excluded) when |excluded| = n-1:
max(low)
for low in the only permitted array
(2)
f(low, excluded):
max(
min(
a - low,
f(a, excluded')
)
)
for a ≥ low, a not in excluded'
where excluded' = excluded ∪ {low's array}
我们可以限制a
。一方面,我们可以达到的最大值是
(3)
m = (highest - low) / (n - |excluded| - 1)
这意味着 a
不需要高于 low + m
。
其次,我们可以存储所有 f(a, excluded')
的结果,由 excluded'
键控(我们有 2^10 个可能的键),每个都在按 a
排序的装饰二叉树中。装饰将是右子树中可实现的最高结果,这意味着我们可以在对数时间内找到所有 f(v, excluded'), v ≥ a
的最大值。
后者建立了支配关系,显然我们对两者都感兴趣更大的a
和更大的f(a, excluded')
以最大化min
函数在(2)中。在中间挑一个a
,我们可以使用二分查找。如果我们有:
a - low < max(v, excluded'), v ≥ a
where max(v, excluded') is the lookup
for a in the decorated tree
然后我们向右看,因为 max(v, excluded)
表示右边有更好的答案,其中 a - low
也更大。
如果我们有:
a - low ≥ max(v, excluded), v ≥ a
然后我们记录这个候选人,向左看,因为向右看,答案固定在max(v, excluded)
,假设a - low
不能减少。
为了对范围进行二分查找,[low, low + m]
(见(3)),而不是一开始就合并和标记所有数组,我们可以将它们分开并比较最接近的候选者到 mid
我们目前可以从每个数组中选择 a
。 (树有混合结果,以子集为键。)(这部分的流程对我来说并不完全清楚。)
使用此方法的最坏情况,因为 n = C
似乎是常数
O(C * array_length * 2^C * C * log(array_length) * log(C * array_length))
C * array_length is the iteration on low
Each low can be paired with 2^C inclusions
C * log(array_length) is the separated binary-search
And log(C * array_length) is the tree lookup
简化:
= O(array_length * log^2(array_length))
尽管在实践中,可能有许多尽早退出的死胡同分支,不可能进行完整选择。
以防万一,不清楚,迭代是在选择中固定的最低元素上进行的。换句话说,我们想要所有不同的 low
(和 excluded
)的最佳 f(low, excluded)
。对于自下而上,我们将从最高值向下迭代,以便我们在迭代时存储 a
的结果。
我将给出一个算法,对于给定的距离 d
,将输出是否有可能做出选择,其中任何一对所选数字之间的距离至少为 d
.然后,您可以对算法输出“YES”的最大值 d
进行二进制搜索,以找到问题的答案。
假设给出最小距离d
。这是算法:
for every permutation p of size n do:
last := -infinity
ok := true
for p_i in p do:
x := take the smallest element greater than or equal to last+d in the p_i^th array (can be done efficiently with binary search).
if no such x was found; then
ok = false
break
end
last = x
done
if ok; then
return "YES"
end
done
return "NO"
所以,我们暴力破解数组的顺序。然后,对于每一种可能的顺序,我们使用贪婪的方法按照顺序从每个数组中选择元素。比如你举的例子:
arrays:
[0, 500]
[100, 350]
[200]
并假设 d = 150
。对于排列1 3 2
,我们首先从第1个数组中取0,然后我们在第3个数组中找到大于等于0+150(它是200)的最小元素(它是200),然后我们找到最小的元素在大于或等于 200+150(它是 350)的第二个数组中。因为我们可以从每个数组中找到一个元素,所以算法输出“YES”。但是对于 d = 200
例如,算法将输出“否”,因为 none 可能的排序将导致成功选择。
上述算法的复杂度为 O(n! * n * log(m))
,其中 m
是数组中元素的最大数量。我相信这就足够了,因为 n
非常小。 (对于m = 10^4
、10! * 10 * 13 ~ 5*10^8
。在现代的CPU上可以在一秒内计算出来。)
假设您有 n 个排序的数字数组,您需要从每个数组中选择 一个 个数字,以使 n 个所选元素之间的最小距离最大化。
示例:
arrays:
[0, 500]
[100, 350]
[200]
2<=n<=10
并且每个数组可以有 ~10^3-10^4
个元素。
在此示例中,最大化最小距离的最佳解决方案是选择数字:500、350、200 或 0、200、350,其中最小距离为 150,是每个组合的最大可能值。
我正在寻找一种算法来解决这个问题。我知道我可以对最大最小距离进行二分搜索,但我看不出如何确定是否存在最大最小距离至少为 d 的解决方案,以便二分搜索起作用。我在想也许动态编程会有所帮助,但还没有找到 dp 的解决方案。
当然,生成所有包含 n 个元素的组合效率不高。我已经尝试过回溯,但它很慢,因为它尝试了所有组合。
n ≤ 10 表明我们可以对 n 取指数依赖。这是 O(2n m n) 时间算法,其中 m 是 数组。
我想到的动态规划方法是,对于 数组,计算所有对(最大数量,最小距离) 有效边界,我们必须从每个边界中选择一个数字 子集中的数组。通过有效边界我的意思是如果我们有 两对 (a, b) ≠ (c, d) 且 a ≤ c 且 b ≥ d,则 (c, d) 不在 有效边界。我们希望对这些边界进行分类 快速合并。
具有空子集的基本情况很简单:有一对,(最小 距离 = ∞, 最大数 = −∞).
对于数组的每个非空子集,以某种顺序扩展 包含顺序,我们计算子集中每个数组的边界, 表示该数组贡献的解决方案的子集 最大数量。然后我们合并这些边界。 (天真地这让我们付出了代价 log n 的另一个因素,可能不值得避免麻烦 鉴于 n ≤ 10,但我们可以通过在 开始启用未来的合并以使用分桶。)
从数组的一个子集和另一个数组构造一个新的边界 还涉及合并。我们在开始时初始化一个迭代器 边界(即最小最大数)和开始处的迭代器 数组(即最少的数字)。虽然两个迭代器都没有结束,
- 发出一个候选对(min(最小距离,数组数-最大 数), 数组数).
- 如果最小值小于或等于最小距离,增加 边界迭代器。如果最小值小于或等于数组编号 − 最大数,递增数组迭代器。
剔除候选对以仅留下有效边界。有 一种在代码中执行此操作的优雅方法,但更难以解释。
让我们看一个具有最佳选择的示例,x
(水平数组 A、B、C、D):
A x
B b x b
C x c
D d x
我们基于范围的递归可以是:让f(low, excluded)
表示没有excluded
中元素的子集的两个选定元素(从数组1到n
)之间的最大最近距离,其中 low
是最低的选择元素。那么:
(1)
f(low, excluded) when |excluded| = n-1:
max(low)
for low in the only permitted array
(2)
f(low, excluded):
max(
min(
a - low,
f(a, excluded')
)
)
for a ≥ low, a not in excluded'
where excluded' = excluded ∪ {low's array}
我们可以限制a
。一方面,我们可以达到的最大值是
(3)
m = (highest - low) / (n - |excluded| - 1)
这意味着 a
不需要高于 low + m
。
其次,我们可以存储所有 f(a, excluded')
的结果,由 excluded'
键控(我们有 2^10 个可能的键),每个都在按 a
排序的装饰二叉树中。装饰将是右子树中可实现的最高结果,这意味着我们可以在对数时间内找到所有 f(v, excluded'), v ≥ a
的最大值。
后者建立了支配关系,显然我们对两者都感兴趣更大的a
和更大的f(a, excluded')
以最大化min
函数在(2)中。在中间挑一个a
,我们可以使用二分查找。如果我们有:
a - low < max(v, excluded'), v ≥ a
where max(v, excluded') is the lookup
for a in the decorated tree
然后我们向右看,因为 max(v, excluded)
表示右边有更好的答案,其中 a - low
也更大。
如果我们有:
a - low ≥ max(v, excluded), v ≥ a
然后我们记录这个候选人,向左看,因为向右看,答案固定在max(v, excluded)
,假设a - low
不能减少。
为了对范围进行二分查找,[low, low + m]
(见(3)),而不是一开始就合并和标记所有数组,我们可以将它们分开并比较最接近的候选者到 mid
我们目前可以从每个数组中选择 a
。 (树有混合结果,以子集为键。)(这部分的流程对我来说并不完全清楚。)
使用此方法的最坏情况,因为 n = C
似乎是常数
O(C * array_length * 2^C * C * log(array_length) * log(C * array_length))
C * array_length is the iteration on low
Each low can be paired with 2^C inclusions
C * log(array_length) is the separated binary-search
And log(C * array_length) is the tree lookup
简化:
= O(array_length * log^2(array_length))
尽管在实践中,可能有许多尽早退出的死胡同分支,不可能进行完整选择。
以防万一,不清楚,迭代是在选择中固定的最低元素上进行的。换句话说,我们想要所有不同的 low
(和 excluded
)的最佳 f(low, excluded)
。对于自下而上,我们将从最高值向下迭代,以便我们在迭代时存储 a
的结果。
我将给出一个算法,对于给定的距离 d
,将输出是否有可能做出选择,其中任何一对所选数字之间的距离至少为 d
.然后,您可以对算法输出“YES”的最大值 d
进行二进制搜索,以找到问题的答案。
假设给出最小距离d
。这是算法:
for every permutation p of size n do:
last := -infinity
ok := true
for p_i in p do:
x := take the smallest element greater than or equal to last+d in the p_i^th array (can be done efficiently with binary search).
if no such x was found; then
ok = false
break
end
last = x
done
if ok; then
return "YES"
end
done
return "NO"
所以,我们暴力破解数组的顺序。然后,对于每一种可能的顺序,我们使用贪婪的方法按照顺序从每个数组中选择元素。比如你举的例子:
arrays:
[0, 500]
[100, 350]
[200]
并假设 d = 150
。对于排列1 3 2
,我们首先从第1个数组中取0,然后我们在第3个数组中找到大于等于0+150(它是200)的最小元素(它是200),然后我们找到最小的元素在大于或等于 200+150(它是 350)的第二个数组中。因为我们可以从每个数组中找到一个元素,所以算法输出“YES”。但是对于 d = 200
例如,算法将输出“否”,因为 none 可能的排序将导致成功选择。
上述算法的复杂度为 O(n! * n * log(m))
,其中 m
是数组中元素的最大数量。我相信这就足够了,因为 n
非常小。 (对于m = 10^4
、10! * 10 * 13 ~ 5*10^8
。在现代的CPU上可以在一秒内计算出来。)