在一个未排序的数组中找到两个数字,使它们的和等于给定的数。

54
我们需要在一个数组中找到一对数,使它们的和等于给定的值。
A = {6,4,5,7,9,1,2}

和为10,那么两对数分别是 - {6,4} , {9,1}

我有两种解决方案。

  • 一种O(nlogn)的解法- 先排序,再使用两个迭代器(开始和结束)检查总和。
  • 一种O(n)的解法 - 对数组进行哈希处理。然后检查哈希表中是否存在sum-hash[i]

但是问题在于,虽然第二种解法只需要O(n)的时间,但同时也需要O(n)的空间。

所以,我想知道是否能够在O(n)的时间复杂度和O(1)的空间复杂度下完成此操作。这不是作业!


1
我怀疑这样的算法存在... - Petar Minchev
1
说到连续,我的意思是它们需要按照给定的顺序相互跟随吗?在你的例子中,[6,4]和[9,1]是按顺序排列的...4是6的邻居,1是9的邻居... - Shashank Kadne
2
@TedHopp:如果您在同一次迭代中创建哈希集并检查元素是否存在,则可以轻松解决您描述的问题。[而不是先创建完整的哈希集,然后仅在稍后检查对] - amit
3
我认为在O(1)空间限制下无法实现。 - Shiplu Mokaddim
1
有人能解释一下第一种方法“O(nlogn)解法-排序+使用两个迭代器(起始和结束)进行检查和”的正确性吗?我的意思是,它确实给出了正确的输出,但我没有直观地理解它。为什么这种方法能够正确地工作呢? - Vikas Mangal
显示剩余9条评论
18个回答

19

使用原地基数排序和 OP 的第一种解法,采用 2 个迭代器相向而行。

如果数组中的数字不是某种多精度数字,例如 32 位整数,可以使用几乎没有额外空间(每次通过 1 位)的方法在 2*32 次排序中将它们排序。或者使用 2*8 次排序和 16 个整数计数器(每次通过 4 位)。


两个迭代器解决方案的细节:

第一个迭代器最初指向已排序数组的第一个元素并向前移动。第二个迭代器最初指向数组的最后一个元素并向后移动。

如果迭代器引用的元素之和小于所需值,则将第一个迭代器推进。如果大于所需值,则将第二个迭代器推进。如果等于所需值,则成功。

只需要一次遍历,因此时间复杂度为 O(n)。空间复杂度为 O(1)。如果使用基数排序,整个算法的复杂度将保持不变。


如果您对相关问题感兴趣(涉及多于 2 个数字的总和),请查看“具有固定子集大小的总和子集”“在数组中找到总和最接近给定数的三个元素”


1
我很难说基数排序实际上需要O(N)的时间,因为它取决于机器字的大小。对于固定的机器来说,它是O(N),但更一般地,它是O(N log U),其中U是最大可能表示的整数。 - templatetypedef
1
@EvgenyKluev:看起来很接近所需的内容。但是基数排序是O(kn),并没有比O(nlogn)更好。您能解释一下如何将其称为O(n)吗? - h4ck3d
@EvgenyKluev:如果每个键都有9位数字,则基数的复杂度将会很高。 - h4ck3d
1
@templatetypedef,@NiteeshMehra,一般情况下基数排序的时间复杂度是O(N log U)。计算哈希函数的时间复杂度为O(log U)。填充和使用哈希表的时间复杂度为O(N log U)。由于OP的第二个解决方案假定时间复杂度为O(N),因此我们有O(N log U) = O(N)。或者O(U) = 1。这使我可以假设基数排序的时间复杂度为O(N)。这不是很合理吗?无论如何,如果O(U) > 1,我们根本无法有O(N)的解决方案,因为我们必须处理N log U位(最坏情况)或N log N位(对于均匀分布的数字和N < U的情况平均情况)。 - Evgeny Kluev
@NiteeshMehra,如果每个键都有9个十进制数字,那么它包含大约32位。你可以按照16次排序(如我的答案所解释的),或者你可以通过每次使用8位和256个计数器来进行8次排序。在实践中,如果N很大,则可能比比较排序的O(N log N)更好,否则可能会更差。 - Evgeny Kluev

7
这是来自微软亚洲研究院的一个经典面试问题。如何在未排序的数组中找到两个数等于给定的和。

[1]暴力解法
此算法非常简单。时间复杂度为O(N^2)。

[2]使用二分查找
使用二分查找来找到Sum-arr[i]与每个 arr[i],时间复杂度可以降至 O(N*logN)。

[3]使用哈希
基于[2]算法,使用哈希,时间复杂度可降至O(N),但此解决方案将增加O(N)的哈希空间。

[4]最优算法:

伪代码:

for(i=0;j=n-1;i<j)
   if(arr[i]+arr[j]==sum) return (i,j);
else if(arr[i]+arr[j]<sum) i++;
else j--;
return(-1,-1);

或者

If a[M] + a[m] > I then M--
If a[M] + a[m] < I then m++
If a[M] + a[m] == I you have found it
If m > M, no such numbers exist.

这个问题已经完全解决了吗?并没有。如果数字是N,则此问题将变得非常复杂。

那么问题是:
如何找到给定数字的所有组合情况?

这是一个经典的NP-Complete问题,称为子集和问题。
要理解NP / NPC / NP-Hard,最好阅读一些专业书籍。

参考资料:
[1]http://www.quora.com/Mathematics/How-can-I-find-all-the-combination-cases-with-a-given-number
[2]http://en.wikipedia.org/wiki/Subset_sum_problem


1
你的第二个解决方案是不正确的。我们如何在未排序的数组中进行二分查找? - Hengameh
5
第四个也不对!你的数组没有排序,所以你不能增加或减少索引。你可能会错过一些值。 - Hengameh
@Hengameh:如果我们先对数组进行排序,第二种解决方案就是正确的。请注意,使用适当的排序算法,运行时间仍将为O(N * logN)。 - Ari
@Hengameh:如果我们先对数组进行排序,第四个解决方案也是正确的。显然,在这种情况下,运行时间将不再是O(N),而将是O(N * LogN)。 - Ari
为什么4比3更优?O(N)更好!只有在空间受限的情况下,4才有意义。 - Joseph Garvin
误导性答案 - Infinite

3
for (int i=0; i < array.size(); i++){
  int value = array[i];
  int diff = sum - value; 
  if (! hashSet.contains(diffvalue)){
      hashSet.put(value,value);
  } else{
       printf(sum = diffvalue + hashSet.get(diffvalue));
  } 
}

--------
Sum being sum of 2 numbers.

哈希和你的解决方案有什么区别? - h4ck3d
这仍然是 O(N) 的空间。 - user2494770
4
OP明确表示不需要额外的空间,为什么这个答案会得到点赞? - seeker
尝试使用数组=[1, 2, 10, 3, 4]和总和=20。 - chouaib

2
创建一个字典,其中键为列表中的数字,值为获取所需值所必需的数字。接下来,检查列表中数字对的存在性。
def check_sum_in_list(p_list, p_check_sum):
    l_dict = {i: (p_check_sum - i) for i in p_list}
    for key, value in l_dict.items():
        if key in p_list and value in p_list:
            return True
    return False


if __name__ == '__main__':
    l1 = [1, 3, 7, 12, 72, 2, 8]
    l2 = [1, 2, 2, 4, 7, 4, 13, 32]

    print(check_sum_in_list(l1, 10))
    print(check_sum_in_list(l2, 99))

Output:
True
Flase

版本 2

import random


def check_sum_in_list(p_list, p_searched_sum):
    print(list(p_list))
    l_dict = {i: p_searched_sum - i for i in set(p_list)}
    for key, value in l_dict.items():
        if key in p_list and value in p_list:
            if p_list.index(key) != p_list.index(value):
                print(key, value)
                return True
    return False


if __name__ == '__main__':
    l1 = []
    for i in range(1, 2000000):
        l1.append(random.randrange(1, 1000))

    j = 0
    i = 9
    while i < len(l1):
        if check_sum_in_list(l1[j:i], 100):
            print('Found')
            break
        else:
            print('Continue searching')
            j = i
            i = i + 10

Output:
...
[154, 596, 758, 924, 797, 379, 731, 278, 992, 167]
Continue searching
[808, 730, 216, 15, 261, 149, 65, 386, 670, 770]
Continue searching
[961, 632, 39, 888, 61, 18, 166, 167, 474, 108]
39 61
Finded
[Finished in 3.9s]

1
如果您能添加几行代码来解释您的代码如何回答问题,那将非常好。 - Yannis
在代码中发现了一个 bug,当键和值均为 5 时。添加了切片以部分搜索。 - Howl Blindfolds

2
    public void printPairsOfNumbers(int[] a, int sum){
    //O(n2)
    for (int i = 0; i < a.length; i++) {
        for (int j = i+1; j < a.length; j++) {
            if(sum - a[i] == a[j]){
                //match..
                System.out.println(a[i]+","+a[j]);
            }
        }
    }

    //O(n) time and O(n) space
    Set<Integer> cache = new HashSet<Integer>();
    cache.add(a[0]);
    for (int i = 1; i < a.length; i++) {
        if(cache.contains(sum - a[i])){
            //match//
            System.out.println(a[i]+","+(sum-a[i]));
        }else{
            cache.add(a[i]);
        }
    }

}

1
如果您假设对于要求和的配对而言值M是恒定的,并且数组中的条目为正数,则可以使用M/2指针(O(1)空间)在一次遍历(O(n)时间)中执行以下操作。这些指针标记为P1,P2,...,Pk,其中k=floor(M/2)。然后执行以下操作:
for (int i=0; i<N; ++i) {
  int j = array[i];
  if (j < M/2) {
    if (Pj == 0)
      Pj = -(i+1);   // found smaller unpaired
    else if (Pj > 0)
      print(Pj-1,i); // found a pair
      Pj = 0;
  } else
    if (Pj == 0)
      Pj = (i+1);    // found larger unpaired
    else if (Pj < 0)
      print(Pj-1,i); // found a pair
      Pj = 0;
  }
}

你可以通过将索引存储为基数为N的数字来处理重复条目(例如两个6)。例如,对于M/2,您可以添加条件语句。

  if (j == M/2) {
    if (Pj == 0)
      Pj = i+1;      // found unpaired middle
    else
      print(Pj-1,i); // found a pair
      Pj = 0;
  } 

但现在你有把这些“对”组合起来的问题。


1
Pj 应该更像 P[j] 吗? - svick
有了这些假设,我们不能只创建一个大小为M的直方图/集合数组吗? - amit
3
如果M是固定的,我有一个更简单的算法。在数组上进行M/2次遍历,在第一次遍历中查找1和M-1,在第二次遍历中查找2和M-2,以此类推,在O(NM) = O(N)时间内完成。 - Ali Ferhat
4
M是O(1)似乎有些作弊。如果M是O(1),那么在数组中只会有O(1)个唯一值可以参与求和,因此你只需要花费O(n)的时间找到这些值,然后使用任何算法都将是O(1)的时间和空间复杂度。 - interjay

0
以下代码将在数组中有两个整数与比较的整数相匹配时返回 true。
 function compareArraySums(array, compare){

        var candidates = [];

        function compareAdditions(element, index, array){
            if(element <= y){
                candidates.push(element);
            }
        }

        array.forEach(compareAdditions);

        for(var i = 0; i < candidates.length; i++){
            for(var j = 0; j < candidates.length; j++){
                if (i + j === y){
                    return true;
                }
            }
        }
    }

0
// 使用哈希表的Java实现 import java.io.*;
class PairSum { private static final int MAX = 100000; // 哈希表的最大尺寸
static void printpairs(int arr[],int sum)
{
    // Declares and initializes the whole array as false
    boolean[] binmap = new boolean[MAX];

    for (int i=0; i<arr.length; ++i)
    {
        int temp = sum-arr[i];

        // checking for condition
        if (temp>=0 && binmap[temp])
        {
            System.out.println("Pair with given sum " +
                                sum + " is (" + arr[i] +
                                ", "+temp+")");
        }
        binmap[arr[i]] = true;
    }
}

// Main to test the above function
public static void main (String[] args)
{
    int A[] = {1, 4, 45, 6, 10, 8};
    int n = 16;
    printpairs(A,  n);
}

}


0

Python 2.7 实现:

import itertools
list = [1, 1, 2, 3, 4, 5,]
uniquelist = set(list)
targetsum = 5
for n in itertools.combinations(uniquelist, 2):
    if n[0] + n[1] == targetsum:
    print str(n[0]) + " + " + str(n[1])

输出:

1 + 4
2 + 3

1
我能看到符号的简便性。愿意讨论空间和时间复杂度吗?这些问题是否以O(n)时间和O(1)空间结尾? - greybeard

0

https://github.com/clockzhong/findSumPairNumber

#! /usr/bin/env python
import sys
import os
import re


#get the number list
numberListStr=raw_input("Please input your number list (seperated by spaces)...\n")
numberList=[int(i) for i in numberListStr.split()]
print 'you have input the following number list:'
print numberList

#get the sum target value
sumTargetStr=raw_input("Please input your target number:\n")
sumTarget=int(sumTargetStr)
print 'your target is: '
print sumTarget


def generatePairsWith2IndexLists(list1, list2):
    result=[]
    for item1 in list1:
        for item2 in list2:
            #result.append([item1, item2])
            result.append([item1+1, item2+1])
    #print result
    return result

def generatePairsWithOneIndexLists(list1):
    result=[]
    index = 0
    while index< (len(list1)-1):
        index2=index+1
        while index2 < len(list1):
            #result.append([list1[index],list1[index2]])
            result.append([list1[index]+1,list1[index2]+1])
            index2+=1
        index+=1
    return result


def getPairs(numList, target):
    pairList=[]
    candidateSlots=[] ##we have (target-1) slots 

    #init the candidateSlots list
    index=0
    while index < target+1:
        candidateSlots.append(None)
        index+=1

    #generate the candidateSlots, contribute O(n) complexity
    index=0
    while index<len(numList):
        if numList[index]<=target and numList[index]>=0:
            #print 'index:',index
            #print 'numList[index]:',numList[index]     
            #print 'len(candidateSlots):',len(candidateSlots)
            if candidateSlots[numList[index]]==None:
                candidateSlots[numList[index]]=[index]
            else:
                candidateSlots[numList[index]].append(index)
        index+=1

    #print candidateSlots

    #generate the pairs list based on the candidateSlots[] we just created
    #contribute O(target) complexity
    index=0
    while index<=(target/2):
        if candidateSlots[index]!=None and candidateSlots[target-index]!=None:
            if index!=(target-index):
                newPairList=generatePairsWith2IndexLists(candidateSlots[index], candidateSlots[target-index])
            else:
                newPairList=generatePairsWithOneIndexLists(candidateSlots[index])
            pairList+=newPairList
        index+=1

    return pairList

print getPairs(numberList, sumTarget)

我已经成功地使用Python实现了一种O(n+m)时间和空间成本的解决方案。

"m"表示这两个数字之和需要等于的目标值。

我相信这是最低成本的解决方案。Erict2k使用了itertools.combinations,与我的算法相比,它也会产生类似或更高的时间和空间成本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接