从数字的差异中获得尽可能低的总和

时间:2010-11-21 17:36:58

标签: algorithm language-agnostic

我必须从数字的差异中找到最低的总和。

假设我有4个号码。 1515,1520,1500和1535.最低差值之和为30,因为1535-1520 = 15& amp; 1515 - 1500 = 15和15 + 15 = 30.如果我愿意这样做:1520 - 1515 = 5&& 1535 - 1500 = 35总计是40。

希望你得到它,如果没有,请问我。

任何想法如何编程?我刚刚在网上找到这个,试图从我的语言翻译成英语。听起来很有趣。我不能做暴力,因为编译需要很长时间。我不需要代码,只是想法如何编程或代码片段。

感谢。

修改 我没有发布所有内容......还有一个版本:

我想说8个可能的数字。但我必须只拿其中的6个来赚取最小的金额。例如,数字1731, 1572, 2041, 1561, 1682, 1572, 1609, 1731,最小的总和将是48,但是在这里我只需要从8中获取6个数字。

10 个答案:

答案 0 :(得分:13)

考虑编辑:

首先对列表进行排序。然后使用动态编程解决方案,状态 i n 表示仅考虑第一个 i时 n 差异的最小总和/ em>序列中的数字。初始状态:dp [*] [0] = 0,其他一切=无穷大。使用两个循环:外部循环通过 i 从1循环到 N ,内循环通过 n 从0循环到R(在示例中为3)在您的编辑中 - 这使用3对数字,这意味着6个单独的数字)。你的递归关系是dp [i] [n] = min(dp [i-1] [n],dp [i-2] [n-1] + seq [i] - seq [i-1])。< / p>

你必须要注意处理我忽略的边界情况,但是一般的想法应该可以运行并且将在O( N log N + NR )并使用O( NR )空间。

答案 1 :(得分:13)

solution by marcog是一个正确的,非递归的,多项式时间解决问题的方法 - 这是一个非常标准的DP问题 - 但是,为了完整性,这里有一个证据证明它有效,并且实际的代码是问题。 [@marcog:如果你愿意,可以随意将这个答案的任何部分复制到你自己的;我会删除它。]

证明

让列表为x 1 ,...,x N 。假设列表已排序的wlog。我们试图从列表中找到K(不相交)元素对,以便最小化它们之间的差异。

声明:最佳解决方案始终由连续元素的差异组成 证明:假设您修复了差异所在的元素子集。然后通过proof given by Jonas Kölker,该子集的最优解包含列表中连续元素的差异。现在假设存在对应于不包括连续元素对的子集的解,即解决方案涉及差x j -x i 其中j> i + 1 。然后,我们可以用x i + 1 替换x j 以获得更小的差异,因为
x i ≤x i + 1 ≤x j ⇒x i + 1 -x i < / sub>≤x j -x i
(不用说,如果x i + 1 = x j ,那么取x i + 1 与x j无法区分。)这证明了这一说法。

其余的只是常规的动态编程:使用前n个元素中的k对的最优解决方案根本不使用第n个元素(在这种情况下,它只是使用来自第一个n-的k对的最优解决方案1), 它使用第n个元素,在这种情况下,它是差异x n -x n-1 使用来自第一个n-2的k-1对加上最优解。

整个程序在时间O(N log N + NK)运行,正如marcog所说。 (排序+ DP。)

代码

这是一个完整的程序。我很懒,初始化数组并使用dicts编写Python代码;这是使用实际数组的小log(N)因子。

'''
The minimum possible sum|x_i - x_j| using K pairs (2K numbers) from N numbers
'''
import sys
def ints(): return [int(s) for s in sys.stdin.readline().split()]

N, K = ints()
num = sorted(ints())

best = {} #best[(k,n)] = minimum sum using k pairs out of 0 to n
def b(k,n):
    if best.has_key((k,n)): return best[(k,n)]
    if k==0: return 0
    return float('inf')

for n in range(1,N):
    for k in range(1,K+1):
        best[(k,n)] = min([b(k,n-1),                      #Not using num[n]
                           b(k-1,n-2) + num[n]-num[n-1]]) #Using num[n]

print best[(K,N-1)]

测试它:

Input
4 2
1515 1520 1500 1535
Output
30

Input
8 3
1731 1572 2041 1561 1682 1572 1609 1731
Output
48

答案 2 :(得分:9)

我假设一般问题是这样的:给定2n个整数的列表,输出n对的列表,使得| x - y |的总和所有对(x,y)都尽可能小。

在这种情况下,想法是:

  • 对数字进行排序
  • (numbers[2k], numbers[2k+1])发出k = 0, ..., n - 1

这很有效。证明:

假设您有x_1 < x_2 < x_3 < x_4(可能包含其他值)并输出(x_1, x_3)(x_2, x_4)。然后

  

|x_4 - x_2| + |x_3 - x_1| = |x_4 - x_3| + |x_3 - x_2| + |x_3 - x_2| + |x_2 - x_1| >= |x_4 - x_3| + |x_2 - x_1|

换句话说,输出(x_1, x_2)(x_3, x_4)总是更好,因为您没有多次覆盖x_2x_3之间的空格两次。通过归纳,2n的最小数量必须与第二个最小数量配对;通过对列表其余部分的归纳,对最小邻居进行配对总是最优的,因此我提出的算法草图是正确的。

答案 3 :(得分:6)

订购清单,然后进行差异计算。

编辑:嗨@hey

您可以使用动态编程解决问题。

假设您有LN整数的列表,您必须形成k对(2*k <= N}

构建一个找到列表中最小差异的函数(如果列表已排序,它会更快;)调用它smallest(list l)

构建另一个为两对找到相同的(可能很棘手,但可行)并将其称为smallest2(list l)

让我们定义best(int i, list l)这个函数,为i

列表中的l对提供最佳结果

算法如下:

  1. 最佳(1,L)=最小(L)
  2. best(2,L)= smallest2(L)
  3. for i from 1 to k:
  4. compute min ( 
        stored_best(i-2) - smallest2( stored_remainder(i-2) ),
        stored_best(i-1) - smallest( stored_remainder(i-1) 
    ) and store as best(i)
    store the remainder as well for the chosen solution
    

    现在,问题是一旦你选择了一对,形成边界的两个整数被保留,不能用于形成更好的解决方案。但是通过回顾两个等级你可以保证你允许转换候选人。

    (切换工作由smallest2

    完成

答案 4 :(得分:2)

第1步:计算配对差异

我认为很明显,正确的方法是对数字进行排序,然后对每个数字进行区分 相邻的一对数字。这些差异是导致这种差异的“候选人”差异 最小差异和。使用示例中的数字将导致:

Number Diff
====== ====
1561
        11
1572
         0
1572
        37
1609
        73
1682
        49
1731
         0
1731
       310
2041

将差异保存到数组或表或其他可以维护的数据结构中 差异和导致每种差异的两个数字。将其称为DiffTable。它 应该看起来像:

Index Diff Number1 Number2
===== ==== ======= =======
  1     11    1561    1572
  2      0    1572    1572
  3     37    1572    1609
  4     73    1609    1682
  5     49    1682    1731
  6      0    1731    1731
  7    310    1731    2041

第2步:选择最小差异

如果必须选择所有数字,我们可以在步骤1中选择奇数编号的数字对 指数:1,3,5,7。这是正确的答案。然而, 问题表明选择了一对子集,这使问题变得相当复杂。 在您的示例中,需要选择3个差异(6个数字= 3对= 3个差异),以便:

  • 差异的总和是最小的
  • 参与任何选定差异的数字将从列表中删除。

第二点意味着,如果我们选择Diff 11(索引= 1以上),则数字15611572为 从列表中删除,因此,索引2处的下一个Diff 0无法使用,因为只有1个实例 剩下1572。每当一个 选择Diff,删除相邻的Diff值。这就是为什么只有一种方法可以选择4对 包含八个数字的列表中的数字。

关于我能想到的最小化Diff之和的唯一方法是生成和测试。

以下伪代码概述了要生成的过程 任意大小的DiffTable的所有“合法”索引值集 其中选择任意数量的数字对。一个(或多个)的 生成的索引集将包含DiffTable的索引,产生最小Diff总和。

/* Global Variables */
M = 7    /* Number of candidate pair differences in DiffTable */
N = 3    /* Number of indices in each candidate pair set (3 pairs of numbers) */
AllSets = [] /* Set of candidate index sets (set of sets) */

call GenIdxSet(1, []) /* Call generator with seed values */

/* AllSets now contains candidate index sets to perform min sum tests on */

end

procedure: GenIdxSet(i, IdxSet)
  /* Generate all the valid index values for current level */
  /* and subsequent levels until a complete index set is generated */
  do while i <= M
     if CountMembers(IdxSet) = N - 1 then  /* Set is complete */
        AllSets = AppendToSet(AllSets, AppendToSet(IdxSet, i))
     else                                  /* Add another index */
       call GenIdxSet(i + 2, AppendToSet(IdxSet, i))
     i = i + 1
     end
return

函数CountMembers返回给定集合中的成员数,函数AppendToSet返回一个新集合 其中参数被附加到单个有序集中。例如 AppendToSet([a, b, c], d)返回集合:[a, b, c, d]

对于给定的参数,M = 7且N = 3,AllSet变为:

[[1 3 5]
 [1 3 6]  <= Diffs = (11 + 37 + 0) = 48
 [1 3 7]
 [1 4 6]
 [1 4 7]
 [1 5 7]
 [2 4 6]
 [2 4 7]
 [2 5 7]
 [3 5 7]]

使用每组索引计算总和,最小的索引标识 DiffTable中所需的数字对。上面我展示了第二组指数给出的 你正在寻找的最低限度。

这是一种简单的蛮力技术,它不能很好地扩展。如果你有一份清单 50个数字对想要选择5对,AllSets将包含1,221,759套 数字对要测试。

答案 5 :(得分:2)

我知道你说你不需要代码,但这是我描述基于集合的解决方案的最佳方式。该解决方案在SQL Server 2008下运行。代码中包含您提供的两个示例的数据。 sql解决方案可以使用单个自联接表完成,但我发现在有多个表时更容易解释。

    --table 1 holds the values

declare @Table1 table (T1_Val int)
Insert @Table1 
--this data is test 1
--Select (1515) Union ALL
--Select (1520) Union ALL
--Select (1500) Union ALL
--Select (1535) 

--this data is test 2
Select (1731) Union ALL
Select (1572) Union ALL
Select (2041) Union ALL
Select (1561) Union ALL
Select (1682) Union ALL
Select (1572) Union ALL
Select (1609) Union ALL
Select (1731) 
--Select * from @Table1

--table 2 holds the sorted numbered list
Declare @Table2 table (T2_id int identity(1,1), T1_Val int)
Insert @Table2 Select T1_Val from @Table1 order by T1_Val

--table 3 will hold the sorted pairs
Declare @Table3 table (T3_id int identity(1,1), T21_id int, T21_Val int, T22_id int, T22_val int)
Insert @Table3
Select T2_1.T2_id, T2_1.T1_Val,T2_2.T2_id, T2_2.T1_Val from @Table2 AS T2_1
LEFT Outer join @Table2 AS T2_2 on T2_1.T2_id = T2_2.T2_id +1

--select * from @Table3
--remove odd numbered rows
delete from @Table3 where T3_id % 2 > 0 

--select * from @Table3
--show the diff values
--select *, ABS(T21_Val - T22_val) from @Table3
--show the diff values in order
--select *, ABS(T21_Val - T22_val) from @Table3 order by ABS(T21_Val - T22_val)
--display the two lowest
select TOP 2 CAST(T22_val as varchar(24)) + ' and ' + CAST(T21_val as varchar(24)) as 'The minimum difference pairs are'
, ABS(T21_Val - T22_val) as 'Difference'
from @Table3
ORDER by ABS(T21_Val - T22_val) 

答案 6 :(得分:0)

我认为@ marcog的方法可以进一步简化。

采用@ jonas-kolker证明找到最小差异的基本方法。获取结果列表并对其进行排序。从该列表中获取R个最小的条目,并将它们用作差异。证明这是最小的数额是微不足道的。

@ marcog的方法实际上是O(N ^ 2),因为R == N是合法的选择。这种方法应该是(2 *(N log N))+ N aka O(N log N)。

这需要一个小的数据结构来保存差异及其派生的值。但是,每次进入都是不变的。因此,空间是O(N)。

答案 7 :(得分:0)

我会回答marcog,您可以使用任何排序算法进行排序。但是现在很难分析。

如果您必须选择N个数字中的R数字,以便它们的差异总和最小,那么数字将按顺序选择,而不会遗漏任何数字。

因此,在对数组进行排序后,您应该运行一个从0到N-R的外循环和一个从0到R-1次的内循环来计算差异的总和。

如果需要,您应该尝试一些例子。

答案 8 :(得分:0)

我采用了一种使用递归算法的方法,但它确实采用了其他人贡献的一些方法。

首先,我们对数字进行排序:

[1561,1572,1572,1609,1682,1731,1731,2041]

然后我们计算差异,跟踪导致每个差异的数字的索引:

[(11,(0,1)),(0,(1,2)),(37,(2,3)),(73,(3,4)),(49,(4,5)),(0,(5,6)),(310,(6,7))]

所以我们得到11来得到索引0处的数字与索引1处的数字之间的差异37,指数2和数字处的数字之间的差异。 3。

然后我对这个列表进行了排序,因此它告诉我哪些对给了我最小的区别:

[(0,(1,2)),(0,(5,6)),(11,(0,1)),(37,(2,3)),(49,(4,5)),(73,(3,4)),(310,(6,7))]

我们在这里看到的是,鉴于我们要选择 n 数字,一个天真的解决方案可能是选择此列表中的第一个 n / 2项。麻烦的是,在这个列表中第三个项目与第一个项目共享一个索引,所以我们实际上只得到5个数字,而不是6.在这种情况下,你需要选择第四个项目以获得一组6个数字。

从这里开始,我想出了这个算法。在整个过程中,有一组接受的索引从空开始,还有一些数字可供选择 n

  1. 如果 n 为0,我们就完成了。
  2. 如果 n 为1,并且第一项只提供1个不在我们的集合中的索引,我们将采取第一项,我们就完成了。
  3. 如果 n 是2或更多,并且第一项将提供2个不在我们集合中的索引,我们采取第一项,然后我们递归(例如转到1)。这一次寻找 n - 2个数字,这些数字在列表的其余部分中产生最小的差异。
  4. 这是基本的例行程序,但生活并不那么简单。有些情况我们尚未涉及,但请确保在继续之前得到这个想法。

    实际上第3步是错误的(发现就在我发布之前: - /),因为可能没有必要包括早期差异来覆盖后来的基本差异所涵盖的指数。第一个例子([1515,1520,1500,1535])违反了这一点。因此,我在下面的部分中抛弃了它,并扩展了第4步来处理它。

    所以,现在我们来看一下特殊情况:

    1. **如上**
    2. **如上**
    3. 如果 n 为1,但第一项将提供两个索引,我们无法选择它。我们必须扔掉那件物品并递归。这次我们仍然在寻找 n 指数,并且我们接受的集合没有变化。
    4. 如果 n 为2或更多,我们可以选择。我们可以a)选择此项目,并递归查找 n - (1或2)索引,或b)跳过此项目,并递归寻找 n 索引。
    5. 4是它变得棘手的地方,并且这个例程变成了搜索,而不仅仅是一个排序练习。我们如何决定采用哪个分支(a或b)?好吧,我们是递归的,所以让我们两个都打电话,看看哪一个更好。我们如何判断他们?

      • 我们希望将任何一个分支产生最低金额。
      • ...但只有当它会消耗正确数量的索引时才会使用。

      所以第4步变成了这样的(伪代码):

      x       = numberOfIndicesProvidedBy(currentDifference)
      branchA = findSmallestDifference (n-x, remainingDifferences) // recurse looking for **n-(1 or 2)**
      branchB = findSmallestDifference (n  , remainingDifferences) // recurse looking for **n** 
      sumA    = currentDifference + sumOf(branchA)
      sumB    =                     sumOf(branchB) 
      
      validA  = indicesAddedBy(branchA) == n
      validB  = indicesAddedBy(branchB) == n
      
      if not validA && not validB then return an empty branch
      
      if validA && not validB then return branchA
      if validB && not validA then return branchB
      
      // Here, both must be valid.
      if sumA <= sumB then return branchA else return branchB
      

      我在Haskell中对此进行了编码(因为我正试图擅长)。我不确定发布整个事情,因为它可能比实用更令人困惑,但这是主要部分:

      findSmallestDifference = findSmallestDifference' Set.empty
      
      findSmallestDifference' _     _ [] = []
      findSmallestDifference' taken n (d:ds)
          | n == 0                = []    -- Case 1
          | n == 1 && provides1 d = [d]   -- Case 2
          | n == 1 && provides2 d = findSmallestDifference' taken n ds -- Case 3
          | provides0 d           = findSmallestDifference' taken n ds -- Case 3a (See Edit)
          | validA && not validB             = branchA -- Case 4
          | validB && not validA             = branchB -- Case 4
          | validA && validB && sumA <= sumB = branchA -- Case 4
          | validA && validB && sumB <= sumA = branchB -- Case 4
          | otherwise             = []                 -- Case 4
              where branchA = d : findSmallestDifference' (newTaken d) (n - (provides taken d)) ds
                    branchB = findSmallestDifference' taken n ds
                    sumA    = sumDifferences branchA
                    sumB    = sumDifferences branchB
                    validA  = n == (indicesTaken branchA)
                    validB  = n == (indicesTaken branchA)
                    newTaken x = insertIndices x taken 
      

      希望你能看到那里的所有案例。那个代码(-ish),加上一些包装器产生了这个:

      *Main> findLeastDiff 6 [1731, 1572, 2041, 1561, 1682, 1572, 1609, 1731]
      Smallest Difference found is 48
            1572 -   1572 =      0
            1731 -   1731 =      0
            1572 -   1561 =     11
            1609 -   1572 =     37
      *Main> findLeastDiff 4 [1515, 1520, 1500,1535]
      Smallest Difference found is 30
            1515 -   1500 =     15
            1535 -   1520 =     15
      

      这已经很久了,但我试图明确。希望这是值得的。


      编辑:可以添加案例3a以避免一些不必要的工作。如果当前差异不提供额外的索引,则可以跳过它。这在上面的步骤4中得到了解决,但是没有必要评估树的两半没有增益。我已将此添加到Haskell。

答案 9 :(得分:0)

这样的东西
  1. 排序列表
  2. 查找重复项
  3. 将副本设为一对
  4. 从列表
  5. 中删除重复项
  6. 将其余列表分成两对
  7. 计算每对的差异
  8. 获取最低金额
  9. 在你的例子中,你有8个数字,需要最好的3对。首先排序给你的列表

    1561, 1572, 1572, 1609, 1682, 1731, 1731, 2041
    

    如果您有重复项,请将它们成对并从列表中删除它们,以便

    [1572, 1572] = 0
    [1731, 1731] = 0
    L = { 1561, 1609, 1682, 2041 }
    

    将剩余的列表分成几组,为您提供以下4对

    [1572, 1572] = 0
    [1731, 1731] = 0
    [1561, 1609] = 48
    [1682, 2041] = 359
    

    然后删除您需要的数字。

    这为您提供以下3对最低配对

    [1572, 1572] = 0
    [1731, 1731] = 0
    [1561, 1609] = 48
    

    所以

    0 + 0 + 48 = 48