直觉上,最佳序列的“中心”将是算术平均值,但并非如此。让我们使用一些向量数学找到正确的解决方案:
第一部分:假设第一个数字不变(我们稍后再处理这个假设),计算差异,因此
1 12 3 14 5 16
-
1 2 3 4 5 6
将产生
0 -10 0 -10 0 -10
。
旁注:请注意,“连续”的数组根据您的“暗示”的定义将是具有差为1的递增算术序列。(请注意,对于您的问题,还有其他合理的解释:有些人可能认为
5 4 3 2 1
是连续的,或者
5 3 1
是连续的,或者
1 2 3 2 3
是连续的。 您也没有指定是否应特别处理负数。)
定理:连续数字必须位于最小和最大数字之间。[证明留给读者]
第二部分:回到我们的例子,假设我们采取了30步(sum(abs(0 -10 0 -10 0 -10
))=30)来将1 12 3 14 5 16
转换成1 2 3 4 5 6
。这是一个正确的答案。但是0 -10 0 -10 0 -10
+c也是一个答案,它产生了一个差为1的等差数列,其中常数项为c。为了最小化“步数”,我们必须选择一个适当的c。在这种情况下,每次增加或减少c,步数就会增加N=6(向量的长度)。例如,如果我们想将原始序列1 12 3 14 5 16
转换为3 4 5 6 7 8
(c=2),那么差值将会是2 -8 2 -8 2 -8
,而sum(abs(2 -8 2 -8 2 -8))=30
。
现在,如果你能将其形象化地描绘出来,那么这就非常清楚了,但在文字中难以表达。首先,我们取得了差向量。想象一下,你可以将它画成这样:
4|
3| *
2| * |
1| | | *
0+--+--+--+--+--*
-1| |
-2| *
我们可以通过将所有元素加或减1来“移动”这个向量。这相当于找到c。我们希望找到使得您看到的 | 的数量最小的偏移量(即曲线与x轴之间的面积)。这不是平均数(那将是
最小化标准差或均方根误差,而不是绝对误差)。为了找到最小的c,让我们将其视为一个函数,并考虑其导数。如果所有差异都远离x轴(我们正在尝试制作101 112 103 114 105 116),那么没有添加这些额外内容是有意义的,因此我们将函数向下移动到x轴。每次我们减少c,我们就可以提高6的解决方案。现在假设一个 * 超过了 x 轴。每次我们减少 c, 我们就可以通过4来提高解决方案(我们省去5个步骤的工作, 但必须为 * 在x轴下方做1个额外的步骤)。当一半的 * 超过了x轴时, 我们将无法进一步改善解决方案(导数: 3-3=0)。(事实上,很快我们开始使解决方案变得更糟,永远不能再改善它。我们不仅找到了这个函数的最小值,而且还可以看到它是全局最小值。)
因此解决方案如下: 假设第一个数字在其位置上。计算差值向量。通过找到差异的中位数并从差异中减去来最小化绝对值之和,以此来实现。得到一个改进后的差异向量。 "改进"向量的绝对值之和就是答案。这是
O(N)
。同样优秀的解决方案(如上所述)将始终是“相邻的”。只有在数字数量为奇数时才存在唯一解,否则如果数字数量为偶数,并且差异的中位数不是整数,则等效最优解将具有任何介于两个中位数之间的校正因子的差异向量。
所以我想最后给出一个例子。
- 输入:
2 3 4 10 14 14 15 100
- 差分向量:
2 3 4 5 6 7 8 9
-2 3 4 10 14 14 15 100
= 0 0 0 -5 -8 -7 -7 -91
- 注意到差分向量的中位数不再在中间,我们需要执行一个
O(N)
中位数查找算法来提取它们...
- 差分向量的中位数是
-5
和-7
- 让我们将-5作为我们的修正因子(任何介于中位数之间的数字,例如-6或-7,也是有效的选择)
- 因此,我们的新目标是
2 3 4 5 6 7 8 9
+5=7 8 9 10 11 12 13 14
,新的差分为5 5 5 0 -3 -2 -2 -86
*
- 这意味着我们需要做
5+5+5+0+3+2+2+86
=108步
我们可以通过重复第二步骤并使用新目标来获得这个结果,或者通过将前一个差值的每个数字加5来获得...但由于您只关心总和,所以我们只需将8*5(向量长度乘以正确因子)加到先前计算的总和中
或者,我们也可以将-6或-7作为我们的校正因子。假设我们取了-7...
- 那么新目标就是
2 3 4 5 6 7 8 9
+7=9 10 11 12 13 14 15 16
,新差值将会是7 7 7 2 1 0 0 -84
- 这意味着我们需要进行7+7+7+2+1+0+0+84=108步,与上面相同
如果您自己模拟此过程,则可以看到步数随着偏移量远离范围[-5,-7]而变得大于108。
伪代码:
def minSteps(array A of size N):
A' = [0,1,...,N-1]
diffs = A'-A
medianOfDiffs = leftMedian(diffs)
return sum(abs(diffs-medianOfDiffs))
Python:
leftMedian = lambda x:sorted(x)[len(x)//2]
def minSteps(array):
target = range(len(array))
diffs = [t-a for t,a in zip(target,array)]
medianOfDiffs = leftMedian(diffs)
return sum(abs(d-medianOfDiffs) for d in diffs)
编辑:
事实证明,对于由不同的整数组成的数组,这等价于更简单的解决方案:选择一个(最多两个)中位数,假设它不动,然后移动其他数字。如果有任何重复项,则此简单方法通常会给出错误的答案,但问题并没有要求,因此这将是一种更简单、更优雅的解决方案。此外,我们可以使用我在此解决方案中给出的证明来证明“假设中位数不动”解决方案如下:校正因子将始终位于数组的中心(即差异的中位数将来自数字的中位数)。因此,任何保证这一点的限制都可以用来创建这个谜题的变化。