使整数数组连续所需的最小步骤

时间:2012-02-19 11:32:07

标签: algorithm

给定一个不同整数的排序数组,使整数连续所需的最小步数是多少?这里的条件是:在一个步骤中,只能改变一个元素,可以增加或减少1。例如,如果我们有2,4,5,6那么'2'可以变为'3',从而使元素连续(3,4,5,6)。因此这里的最小步数为1。同样对于数组:2,4,5,8

  • 步骤1:'2'可以成为'3'
  • 第2步:'8'可以做成'7'
  • 第3步:'7'可以成为'6'

因此,序列现在为3,4,5,6,步数为3。

我尝试如下但不确定它是否正确?

    //n is the number of elements in array a
    int count=a[n-1]-a[0]-1;
    for(i=1;i<=n-2;i++)
    {
        count--;
    }
    printf("%d\n",count);

感谢。

7 个答案:

答案 0 :(得分:10)

直观的猜测是&#34;中心&#34;最佳序列的最大序列将是算术平均值,但事实并非如此。让我们通过一些矢量数学找到正确的解决方案:

第1部分:假设第一个数字是单独存在的(我们稍后将处理此假设),计算差异,因此1 12 3 14 5 16 - 1 2 3 4 5 6将产生{{1} }。

旁注:请注意&#34;连续&#34; 暗示定义的数组将是一个增加的算术序列,差异为1.(请注意,您的问题还有其他合理的解释:有些人可能认为0 -10 0 -10 0 -10是连续的,或{{ 1}}是连续的,或5 4 3 2 1是连续的。你也没有说明是否应该区别对待负数。)

定理:连续数字必须位于最小和最大数字之间。 [证据留给读者]

第2部分:现在回到我们的例子,假设我们采取了将5 3 1转换为1 2 3 2 3所需的30步(sum(0 -10 0 -10 0 -10))= 30)。这是一个正确的答案。但是1 12 3 14 5 16 + c也是一个答案,对于任何常数c,它产生差值1的算术序列。为了最大限度地减少&#34;步骤&#34;的数量,我们必须选择一个合适的c。在这种情况下,每次我们增加或减少c时,我们将步数增加N = 6(向量的长度)。例如,如果我们想将原始序列1 2 3 4 5 6转换为0 -10 0 -10 0 -10(c = 2),则差异可能是1 12 3 14 5 163 4 5 6 7 8

现在,如果您可以直观地描绘它,这一点非常清楚,但在文本中输入很难。首先我们采用差异向量。想象一下你是这样绘制的:

2 -8 2 -8 2 -8

我们可以自由地转移&#34;通过在所有内容中添加或减去1来上下移动此向量。 (这相当于找到c。)我们希望找到最小化您看到的sum(abs(2 -8 2 -8 2 -8))=30数量的偏移(曲线和x轴之间的区域)。这不是平均值(即minimizing the standard deviation or RMS error,而不是绝对误差)。为了找到最小化c,让我们将其视为一个函数并考虑其导数。如果差异都远离x轴(我们试图制作 4| 3| * 2| * | 1| | | * 0+--+--+--+--+--* -1| | -2| * ),那么只是不添加这些额外的东西是有意义的,所以我们将函数向下移向x轴。每次我们减少c,我们将解决方案改进6.现在假设其中一个|通过x轴。每次我们减少c,我们将解决方案改进5-1 = 4(我们保存了5个工作步骤,但是必须为x轴下方的101 112 103 114 105 116做一个额外的工作步骤)。最终当HALF *超过x轴时,我们不能再提高解决方案(衍生:3-3 = 0)。 (事实上​​,我们很快就会开始使解决方案变得更糟,并且再也不会让它变得更好。我们不仅找到了这个函数的最小值,而且我们可以看到它是全局最小值。)

因此解决方案如下:假装第一个数字到位。计算差异的向量。最小化此向量的绝对值之和;通过找到差异的中位数并从差异中减去差异来获得改进的差异向量。 &#34;改进的绝对值的总和&#34;矢量是你的答案。这是* 相同最优性的解决方案(如上所述)将始终为&#34;相邻&#34;。只有存在奇数个数字时才存在唯一的解决方案;否则,如果存在偶数个数,并且差的中值不是整数,则同样最优的解将具有在两个中位数之间具有任意数的校正因子的差异向量。

所以我想如果没有最后一个例子,这将是不完整的。

  1. 输入:*
  2. 差异向量:O(N) - 2 3 4 10 14 14 15 100 = 2 3 4 5 6 7 8 9
  3. 请注意,差异向量的中位数不再位于中间位置,我们需要执行2 3 4 10 14 14 15 100中位数查找算法来提取它们...
  4. 差异向量的中位数为0 0 0 -5 -8 -7 -7 -91O(N)
  5. 让我们把-5作为我们的修正系数(中位数之间的任何数字,例如-6或-7,也是一个有效的选择)
  6. 因此我们的新目标是-5 + 5 = -7,新的差异为2 3 4 5 6 7 8 9 *
  7. 这意味着我们需要7 8 9 10 11 12 13 14 = 108步
  8. *(我们通过使用我们的新目标重复步骤2,或者通过在先前差异的每个数字上加5来获得此...但由于您只关心总和,我们只需添加8 * 5 (向量长度乘以正确因子)到先前计算的总和)

    或者,我们也可以将-6或-7作为我们的修正因子。我们说我们采取了-7 ......

    • 然后新目标将是5 5 5 0 -3 -2 -2 -86 + 7 = 5+5+5+0+3+2+2+86,新的差异将是2 3 4 5 6 7 8 9
    • 这意味着我们需要做7 + 7 + 7 + 2 + 1 + 0 + 0 + 84 = 108步,与上述相同

    如果您自己模拟,可以看到步数变为&gt; 108,因为我们偏离范围[-5,-7]进一步偏移。

    伪代码:

    9 10 11 12 13 14 15 16

    的Python:

    7 7 7 2 1 0 0 -84

    修改

    事实证明,对于不同整数的数组,这相当于simpler solution选择其中一个(最多2个)中位数,假设它没有&#39 ; t移动,并相应地移动其他数字。如果您有任何重复项,这种更简单的方法通常会给出不正确的答案,但是OP并没有问这个问题,因此这将是一个更简单,更优雅的解决方案。此外,我们可以使用此解决方案中给出的证明来证明&#34;假设中位数没有移动&#34;解决方案如下:校正因子将始终位于数组的中心(即差异的中位数将来自数字的中位数)。因此,任何可以保证这一点的限制都可以用来创造这种脑力激荡的变体。

答案 1 :(得分:7)

获得所有数字的中位数之一。由于数字已经排序,这应该不是什么大问题。假设中位数不移动。然后计算相应移动所有数字的总成本。这应该给出答案。

社区编辑:

def minSteps(a):
    """INPUT: list of sorted unique integers"""

    oneMedian = a[floor(n/2)]

    aTarget = [oneMedian + (i-floor(n/2)) for i in range(len(a))]
      # aTargets looks roughly like [m-n/2?, ..., m-1, m, m+1, ..., m+n/2]

    return sum(abs(aTarget[i]-a[i]) for i in range(len(a)))

答案 2 :(得分:3)

这可能不是一个理想的解决方案,而是第一个想法。

给定排序序列[x 1 ,x 2 ,...,x n ]:

  • 编写一个函数,将元素的差异返回到上一个元素和下一个元素,即( x n - x n -1 x n +1 - x n )。

  • 如果与前一个元素的差异是> 1,您必须通过 x n - x n增加所有先前的元素 -1 - 1.也就是说,必要步骤的数量会增加前一个元素的数量×( x n - x n -1 - 1)。我们将此号码称为 a

  • 如果与下一个元素的差异大于1,则必须通过 x n +1来减少所有后续元素 - x n - 1.也就是说,必要步骤的数量会增加后续元素的数量×( x n +1 - x n - 1)。我们将此号码称为 b

  • 如果 a &lt; b ,然后增加所有先前的元素,直到它们与当前元素连续。如果 a &gt; b ,然后减少所有后续元素,直到它们与当前元素连续。如果 a = b ,则选择这两项操作中的哪一项无关紧要。

  • 添加上一步中采取的步骤数(通过 a b 增加必要步骤的总数),并重复直到所有元素都是连续的。

答案 3 :(得分:1)

首先,假设我们选择一个连续增加值的任意目标,然后计算修改数组要匹配的数组的成本(所需步骤数)。

Original:        3   5   7   8  10  16
Target:          4   5   6   7   8   9
Difference:     +1   0  -1  -1  -2  -7     -> Cost = 12
Sign:            +   0   -   -   -   -

因为输入数组已经有序且不同,所以它严格增加。因此,可以证明差异总是不会增加。

如果我们通过将目标增加1来更改目标,则成本会发生变化。当前差异为正或零的每个位置将导致成本增加1.当前差异为负的每个正数将使成本减少1:

Original:        3   5   7   8  10  16
New target:      5   6   7   8   9  10
New Difference: +2  +1   0   0  -1  -6     -> Cost = 10  (decrease by 2)

相反,如果我们将目标减少1,则差异当前为正的每个位置将使成本减少1,而差异为零或负数的每个位置将导致成本增加1 :

Original:        3   5   7   8  10  16
New target:      3   4   5   6   7   8
New Difference:  0  -1  -2  -2  -3  -8     -> Cost = 16  (increase by 4)

为了找到目标数组的最佳值,我们必须找到一个目标,这样任何变化(增量或减量)都不会降低成本。注意,当具有负差异的位置多于具有零或正差异的位置时,目标的增量仅可以降低成本。当具有正差异的位置多于具有零或负差异的位置时,减量只能降低成本。

以下是差异符号的一些示例分布。请记住,差异数组是不增加的,所以积极总是必须是第一个和负数最后:

        C   C
+   +   +   -   -   -       optimal
+   +   0   -   -   -       optimal
0   0   0   -   -   -       optimal
+   0   -   -   -   -       can increment (negatives exceed positives & zeroes)
+   +   +   0   0   0       optimal
+   +   +   +   -   -       can decrement (positives exceed negatives & zeroes)
+   +   0   0   -   -       optimal
+   0   0   0   0   0       optimal
        C   C

观察如果其中一个中心元素(标记为C)为零,则目标必须是最佳的。在这种情况下,充其量任何增量或减量都不会改变成本,但可能会增加成本。这个结果很重要,因为它给了我们一个简单的解决方案。我们选择一个目标,a[n/2]保持不变。可能有其他可能的目标产生相同的成本,但肯定没有更好的目标。以下是修改后的原始代码来计算此费用:

//n is the number of elements in array a
int targetValue;
int cost = 0;
int middle = n / 2;
int startValue = a[middle] - middle;
for (i = 0; i < n; i++)
{
    targetValue = startValue + i;
    cost += abs(targetValue - a[i]);
}
printf("%d\n",cost);

答案 4 :(得分:0)

你不能通过在阵列上迭代一次来做到这一点,这是肯定的 您首先需要检查每两个数字之间的差异,例如:
2,7,8,9可以2,3,4,5包含18个步骤,6,7,8,9包含4个步骤 创建一个新的数组,区别如下:对于2,7,8,9,它将是4,1,1。现在您可以决定是增加还是减少第一个数字。

答案 5 :(得分:0)

让我们假设连续数组看起来像这样 -

c c + 1 c + 2 c + 3 ..依此类推

现在举个例子 -

5 7 8 10

在这种情况下,连续数组将是 -

c c + 1 c + 2 c + 3

为了得到最小步数,第i个指数的整数(前后)的差值模数之和应该是最小值。在这种情况下,

(c-5)^ 2 +(c-6)^ 2 +(c-6)^ 2 +(c-7)^ 2应该是最小的

设f(c)=(c-5)^ 2 +(c-6)^ 2 +(c-6)^ 2 +(c-7)^ 2          = 4c ^ 2 - 48c + 146

应用微积分来获得最小值,

f'(c)= 8c - 48 = 0 =&GT; c = 6

所以我们的连续数组是6 7 8 9,这里的最低成本是2。

总结一下,只需生成f(c),得到第一个差分并找出c。 这应该是O(n)。

答案 6 :(得分:-1)

蛮力逼近O(N*M)

如果通过数组a中的每个点绘制一条线,则y0是一个值,其中每一行从索引0开始。然后答案是从ay0的每一行所需的步骤数在Python中的最小值:

y0s = set((y - i) for i, y in enumerate(a))
nsteps = min(sum(abs(y-(y0+i)) for i, y in enumerate(a))
             for y0 in xrange(min(y0s), max(y0s)+1)))

输入

2,4,5,6
2,4,5,8

Output

1
3