【问题标题】:Minimize max - min after picking 1 element each from N arrays在从 N 个数组中选择 1 个元素后最小化 max - min
【发布时间】:2012-10-18 21:39:10
【问题描述】:

给定n 排序数组,通过从每个数组中选择一个数字来创建一个新数组,以使新数组中数字的最大值和最小值之间的差异最小化。

n=3 输入数组示例:

A = {4, 10, 15}
B = {1, 13, 29}
C = {5, 14, 28}

在这种情况下,答案是从数组 A 中选择 15,从数组 B 中选择 13,从数组 C 中选择 14,因为 max[{15, 13, 14}] - min[{15, 13, 14}] = 15 - 13 = 2 并且没有其他组合可以得到最大值-最小差值小于 2。

什么是最有效的算法?

【问题讨论】:

  • 不应该是max - min = 15 - 13 = 2吗?
  • 数组总是排序的吗?
  • 非常相关的问题:stackoverflow.com/questions/6567368
  • 我澄清了这个问题,然后我认为它不应该被关闭。
  • @hexist,是的,数组总是排序的。我编辑了问题。

标签: algorithm


【解决方案1】:

让, A1, A2, ... 有 n 个数组。
假设所有数组都已排序,否则我们可以单独排序。

S = [ A1[0], A2[0],...An[0] ]
minSpread = max{S} - min{S}
Iterate i = 1 to L (where L is length of shortest array)
   Remove min{S} from S.
   Insert Ak[i] in S. (where Ak is the kth array from which a value was removed in previous step.)
   minSpread = min(minSpread, max{S} - min{S});

由于我们必须最小化传播(最大-最小),我们唯一的选择是通过删除当前最小值来“向上”挤压最小值。

在 O(N) + O(N*L*logN) 中计算,其中 N 为否。数组的长度,L 是最短数组的长度。

这是显示搜索结果时的常见问题。当我们必须显示包含所有给定搜索词的页面摘录的最小可能窗口时。
这里,A1[] A2[]... An[] 包含单词say-W1, W2... Wn出现的索引。

编辑:

尼莫:你是对的。证明有点牵强。您提供的链接尝试了类似的解决方案。我只能补充:

考虑事实:
1. 我们必须维护每个数组中的一个元素。
2. 数组都是按升序排列的。
因此,帮助我们立即丢弃许多组合,与生成所有组合相比,可以在更好的时间内完成它。 有关详细信息,请访问“Nemo”提供的链接。

并且,通过按照建议维护一个 minheap,可以将复杂性降低到 O(N) + O(N*L*logN)。
其中,N 不是。数组的长度,L 是最短数组的长度。

【讨论】:

  • ???要最小化最大最小值,您还可以“将最大'向下'挤压”。我也不知道“在上一步中删除值的第 k 个数组”是什么意思。你能用一些伪代码对此进行扩展吗?
  • 是的,但我假设数组已经排序。因此,从所有数组的第 0 个索引开始,将没有空间用于“挤压最大值”。第 K 个数组是在结果集 S 中发现其值最小的数组。为了填补这个空白,我们引入了下一个值,因为我们必须从每个数组中保持一个元素。
  • 好的,我相信这是可行的,因为它本质上与stackoverflow.com/questions/6567368 中的算法相同。但它必须起作用并不明显。您没有检查所有可能的组合,那么您如何确定在沿着这些阵列行进时不会错过绝对最小值?证明并不是那么简单的IMO。但是无论如何+1。 (此外,您应该使用“N”以外的其他值作为最短数组的长度,因为问题已经将 N 定义为数组的数量。您的算法实际上是 O(NL) 或 O(NL log L) 如果数组最初没有排序)
  • @Nemo,如果我们在每一步都计算最小值,那么复杂度将是 NLN。主循环最多可以运行 N L 次。在每一步,我们计算最小值。计算 N 个项目的最小值可能需要 O(N) 时间。也许,如果我们使用最小堆,最小操作将是 O(lgN)。因此,复杂度将是 NL*lgN
  • @user674669 堆构建可以在 O(N) 中完成,随后我们可以运行主循环长度为 L,每次执行 logN 步以更新最小值。因此,O(N) + O(L*logN)。
【解决方案2】:
  1. 将所有数组中的所有元素复制到一个新数组中,其中的元素包含原始数组中的值和原始数组的唯一标识符。 O(n)
  2. 按值 O(n log n) 对新数组进行排序
  3. 遍历新数组 - 找到第一个和最后一个成员之间差异最小且包含所有原始数组的成员的序列。 O(n^2)

C#实现

var arr1 = new[] { 4, 10, 15 };
var arr2 = new[] { 1, 13, 29 };
var arr3 = new[] { 5, 14, 28 };

var sortedAndIndexed = arr1
    .Select(x => new { value = x, array = 'a' })
    .Concat(arr2.Select(x => new { value = x, array = 'b' }))
    .Concat(arr3.Select(x => new { value = x, array = 'c' }))
    .OrderBy(x => x.value)
    .ToList();

var numberOfArrays = 3;
var minValue = sortedAndIndexed.Last().value - sortedAndIndexed.First().value;
var bestSlice = new[] { 0, sortedAndIndexed.Count - 1 };
for (int i = 0; i < sortedAndIndexed.Count; i++)
{
    var seen = new HashSet<char>();
    var firstItem = sortedAndIndexed[i];
    seen.Add(firstItem.array);
    int j = i + 1;
    for (; j < sortedAndIndexed.Count && 
           seen.Count < numberOfArrays && 
           sortedAndIndexed[j].value - firstItem.value < minValue; j++)
    {
        seen.Add(sortedAndIndexed[j].array);
    }
    if (seen.Count == numberOfArrays)
    {
        j -= 1;
        int value = sortedAndIndexed[j].value - firstItem.value;
        if (value < minValue)
        {
            minValue = value;
            bestSlice = new[] { i, j };
        }
    }
}

var arraySeen = new HashSet<char>();
var sliceElements = new List<int>();
for (int i = bestSlice[0]; i <= bestSlice[1]; i++)
{
    var item = sortedAndIndexed[i];
    if (arraySeen.Add(item.array))
    {
        sliceElements.Add(item.value);
    }
}
var elements = sliceElements
      .Select(x => x.ToString())
      .ToArray();
var result = String.Join(", ", elements);
Console.WriteLine("Best slice: "+ result);

输出:

Best slice: 13, 14, 15

【讨论】:

    【解决方案3】:

    这可以通过递归回溯方法解决:

    Best = OO
    ChoiceArr = [ ]
    
    function rec(i)
           if (i == numArrays)
              calc()
           else
               for j = 0 to Arrays[i].length - 1
                   ChoiceArr[i] = Arrays[i][j]
                   rec(i + 1)
    
    function calc()
         mn = OO
         mx = -OO
         for i = 0 to ChoiceArr.length - 1
              mn = min(mn, ChoiceArr[i])
              mx = max(mx, ChoiceArr[i])
    
         Best = min(Best, mx - mn)
    

    复杂度为指数:O(m ^ n),其中m是最大子数组大小,n是子数组的总数。随着 n 的增长,这可能会增长得非常快并消耗大量时间。

    【讨论】:

    • 但这一点也不好玩。如何高效地做?
    • @lVlad 我找不到动态编程优化。我很难找到。
    • @Alexander 而不是什么,一个超现实的?那是真实的,而且比实际上使用没有内在含义的魔术字母要好得多。编辑:虚拟 -1 用于您的编辑,以前更好。
    • @IVlad,谁在谈论魔法字母?我无法理解您的评论
    • @Alexander 我的观点是“O(m ^ n),其中 m 是最大子数组大小,n 是子数组的总数”比它更糟糕而且不必要地冗长以前,或者只是O(arraysSize ^ numArrays)
    猜你喜欢
    • 2017-11-03
    • 1970-01-01
    • 1970-01-01
    • 2017-11-28
    • 2011-02-28
    • 2020-08-09
    • 2016-09-30
    • 2010-10-02
    • 1970-01-01
    相关资源
    最近更新 更多