有N个数的数组,找出这个数组中的两个数,使得这两个数的和最接近0

有N个数的数组,没有顺序。现在的问题是让你在数组中找出两个数,使得这两个数的和尽可能的接近0。

想到的的方法是尝试所有数对<xi,xj>的组合,之后找出其中和的绝对值最小的数对即可。但是这样做的时间复杂度是O(N^2),有没有更快一点的方法呢?

这里给出一个O(NlogN)时间复杂度的算法。

有一种比较直观的做法。

对数组排好序之后。如果数字全部是正数,那么取最小的两个数的和。如果数字全部是负数,则取最大的两个数字的和。如果数字有正有负。那么我们必须枚举每一个xi,之后用二分在数组中找小于等于-xi的最大值和大于等于-xi的最小值。能与xi构成和的绝对值最小的数字,肯定就是这两个数字中的某一个。所有的xi都枚举过之后,我们就可以找出和的绝对值最小值了。时间复杂度是O(NlogN)。

还有一种比较简便的做法,但是证明有些复杂。

1:对数组中的数进行从小到大排序。
2:设置两个游标,一个指向第一个数,另一个指向最后一个数,之后进行如下操作:把游标处的两个数相加取绝对值,与记录当前已经得到的最小绝对值比较,小则记录。之后对游标处的数的绝对值进行比较,移动指向数字绝对值较小的游标(如果是左面的游标指向的数字绝对值小,则向右移动,否则右面的游标向左移动)。循环此步骤,直至找到最小绝对值0或者游标相遇。

举个例子:

原始序列:

-2 3 4 -7 9 5

排序后:

-7 -2 3 4 5 9

寻找过程:

res = MAX_INF;

-7 -2 3 4 5 9
|           |         res = 2

-7 -2 3 4 5 9
|         |           res = 2

-7 -2 3 4 5 9
    |     |           res = 2

-7 -2 3 4 5 9
    |   |             res = 2

-7 -2 3 4 5 9
    | |               res = 1

-7 -2 3 4 5 9
    |                 res = 1

  虽然给出了这样的算法,但是怎么证明该算法是正确的?以下的证明很繁琐。

证明:

设排好序的序列为

x[1],x[2],......,x[i],x[i+1],......,x[j],x[j+1],......,x[n-1],x[n]

假设最优解为<x[i],x[j]>

x[1],x[2],......,x[i],x[i+1],......,x[j],x[j+1],......,x[n-1],x[n]
                              |                                   |

如果算法是正确的,那么必须得证明算法执行过程中游标不会错过最优解。

初始时,游标为<1, n>,显然没有错过最优解。之后每步,都有一个游标移动。那么肯定有一个游标会先到达最优位置。例如左面的游标最先到达x[i]的位置,而另一个游标的位置为k>j

x[1],x[2],....,x[i],x[i+1],....,x[j],x[j+1],....x[k],....,x[n-1],x[n]
                 |                                |

下面分情况讨论:

(1): x[i] > 0

这种情况下,显然 x[j] > 0 , x[k] > 0,并且x[i]+x[k] > x[i]+x[j],那么把右面的游标向左移动会更优,并且不会错过最优解。

(2): x[i] < 0,x[j] > 0

这种情况下又分3中小情况。

2.1: x[i]+x[j] > 0

这种情况下,x[i]+x[k] > 0 并且x[i]+x[j] < x[i]+x[k] (因为x[j]<x[k]),那么把右面的游标向左移动会更优,并且不会错过最优解。

2.2: x[i]+x[j] < 0, x[i]+x[k] < 0

这种情况下,显然|x[i]+x[k]| < |x[i]+x[j]|,推出<x[i],x[j]>不是最优解,这与假设相矛盾,所以在遇到最优解之前,这种情况不会出现。

2.3: x[i]+x[j] < 0, x[i]+x[k] > 0

这种情况下有|x[i]+x[j]| < x[i]+x[k]=>-x[i]-x[j]<x[i]+x[k]=>x[j]+x[k] > -2*x[i] => x[k] > -x[i] = |x[i]| (因为x[j] < -x[i])。也就是说,在这种情况下,我们会移动位置为k那个游标,而不会移动位置为i那个游标,所以不会错过最优解。

(3): x[i] < 0, x[j] < 0

这种情况下又继续分成两种小情况。

3.1 x[k] < 0

这种情况下<x[i],x[j]>不是最优解,这与假设相矛盾,所以在遇到最优解之前这种情况不会出现。

3.2 x[k] > 0

这种情况下也分两种小情况。

3.2.1 x[i]+x[k] < 0

|x[i]+x[j]| < |x[i]+x[k]| => -x[i]-x[j] < -x[i]-x[k] => x[j] > x[k],这与x[j] < x[k]相矛盾,因为x是排好序的数组。所以这种情况在遇到最优解之前不会出现。

3.2.2 x[i]+x[k] > 0

|x[i]+x[j]| < |x[i]+x[k]| => -x[i]-x[j] < x[i]+x[k] => x[k]+x[j] > -2*x[i]=> x[k] > -2*x[i]-x[j] => x[k] > |x[i]|。也就是说,在这种情况下,我们会移动位置为k那个游标,而不会移动位置为i那个游标,所以不会错过最优解。

由对称性可证明当右面的游标先到达x[j]时这种情况。

综上所述可知,只要重复算法的过程,那么我们的游标肯定能够到达最优解<i,j>的状态,而我们采用的方法又是不断记录最小值,那么我们肯定能够得到最优解。

 

三楼给出了一个非常简便的方法:按照绝对值排序。仔细想想,其实就是我上面的方法的另一种形式。

比如,

-7 -2 3 4 5 9

按照3楼的方法,将会是 -2 3 4 5 7 9取相邻两数值差绝对值的最小值。如果我们按照绝对值由大到小排序,则序列为9 7 5 4 3 -2,那么这时取相邻两个数的绝对值等价于方法二的两个游标。
posted @ 2012-10-12 20:21  haolujun  阅读(5094)  评论(4编辑  收藏  举报