【问题标题】:Find all pairs of integers within an array which sum to a specified value查找数组中总和为指定值的所有整数对
【发布时间】:2010-12-02 10:07:51
【问题描述】:

设计一种算法来查找数组中所有整数对,总和为指定值。

我已经尝试过使用哈希表来存储数组元素总和的条目,但这不是一个有效的解决方案。

我可以使用什么算法来有效地解决这个问题?

【问题讨论】:

  • 这不是作业问题。谢谢大家的投入。我曾尝试使用哈希表来存储数组元素总和的条目,但这不是有效的解决方案,因此想知道其他 stackoverflow 读者对此有何想法。再次感谢您的意见。
  • 这里接受的答案是......不太理想,因为可以在不增加二分搜索复杂性的情况下解决问题。

标签: algorithm


【解决方案1】:

我不明白为什么哈希表方法效率低下,至少在算法分析方面 - 诚然,在内存局部性方面,它可能非常糟糕。无论如何,扫描数组两次...

第一次扫描 - 将所有数组元素放入哈希表 - 总共 O(n)。单个插入仅摊销 O(1),但关于摊销分析如何工作的一个巧妙之处意味着 O(n) 是绝对的 - 不是摊销的。

第二次扫描 - 检查哈希表中的 (sum - current) - 总共 O(n)。

至少在理论上,这优于 O(n log n) 排序和搜索方法。

然后,请注意,您可以将两个扫描合并为一个。在第一次扫描中遇到第二对时,您可以立即发现一对。在伪代码中...

for i in array.range
  hashset.insert (array [i])

  diff = sum - array [i]
  if hashset.includes (diff)
    output diff, array [i]

如果您需要项目的位置,请使用哈希图并将项目位置存储在其中。如果您需要处理重复项,您可能需要将计数存储在哈希图中。对于位置和重复项,您可能需要位置链接列表的起始指针哈希图。

这是对哈希表实现的假设,但考虑到大多数当前语言和库中的常用实现,这些假设是相当安全的。

顺便说一句 - 组合扫描不应被视为优化。迭代开销应该是微不足道的。对于非常大的数组,内存局部性问题可能会稍微提高单次传递的效率,但无论如何,真正的内存局部性问题将出现在哈希表查找中。

IMO 合并扫描的唯一真正原因是因为您只希望每对报告一次 - 以两次扫描的方式处理会更麻烦。

【讨论】:

  • 关于返回的对是否应该来自唯一数组索引的问题是模棱两可的。当数组中单个值的副本是总和时,前两个解决方案将输出一个解决方案。例如sum=10array=[5,1,2,3]。该解决方案将输出(5,5)。第二种解决方案可以修改为在循环末尾移动插入以避免这种行为。
  • @harshpatel991 - 关于歧义的好点,我从没想过。如果您说我的第一个(两次)方法假设可以两次使用相同的值,那是正确的 - 您可能需要哈希表中的计数或重复检测标志来处理替代解释。跨度>
  • 如果你说我的第二种(一次性)方法也是如此,现在重新阅读它,我只是从一开始就假设你的修复 - 即使包含的代码没有这样做.正如经常发生的那样,很容易忽略您正在做出假设,即使这些假设相互矛盾 - 很明显。
【解决方案2】:
  1. 如果数组已排序:

    令 i = 0, j = 数组结尾,sum = 您要查找的值, 然后做:

    如果 i+j = sum,则输出 (i,j)。
    如果 i+j 如果 i+j > sum,则将 j 向左移动一位。

    时间复杂度:O(n)。空间复杂度:O(1)。

  2. 如果数组没有排序,有几种方法可以解决这个问题:

    1. 对数组进行排序,然后使用上面的方法。

    2. HashMap:

      将所有元素存储在 HashMap 中。

      a+b=sum,所以b=sum-a。对于数组的每个元素a,从HashMap 中查找b

      HashMap 查找需要平均 O(1)。

      时间复杂度:O(n)。空间复杂度:O(n)。

    3. 位图:

      遍历输入以创建一个位图,其中每个位对应一个元素值。假设输入是{2,5,8},然后我们将位图数组的索引 2、5 和 8 从二进制 0 切换到 1。这需要每个元素 O(1),因此总共需要 O(n)。

      再次检查输入。我们知道b=sum-a,因此对于输入中的每个元素a,查找它的b,这可以在O(1) 中完成,因为它是位图索引。这也需要 O(n) 的总和。

      时间复杂度:O(n) + O(n) = O(n)。空间复杂度:位图空间 = O(n)。

【讨论】:

    【解决方案3】:

    您甚至不需要将所有元素存储在 hashmap 中,然后进行扫描。您可以在第一次迭代期间进行扫描。

    void foo(int[] A, int sum) {
        HashSet<Integer> set = new HashSet<Integer>();
        for (int e : A) {
            if (set.contains(sum-e)) {
                System.out.println(e + "," + (sum-e));
                // deal with the duplicated case
                set.remove(sum-e);
            } else {
                set.add(e);
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      对数组进行排序,然后从两端进入如何?

      【讨论】:

      • 这正是我要去的地方。将操作次数保持在最低限度。
      • 可以进一步优化数组本身的排序,方法是在初始传递期间保留最小和最大数字的选项卡,并计算可用于消除的最大(可能还有最小)阈值在下一次迭代中太大(也可能太小)的数字。
      • 只有在假设整数为非负数时才能丢弃太大的数字。你怎么能因为太小而丢弃一个数字?
      • 我同意@Beta。如果你有负数,那就太复杂了。问题中没有提到。这就是我采用全排序和二分搜索的原因。
      【解决方案5】:

      假设所需总和 = R

      1. 对数组进行排序
      2. 对于数组 A(n) 中的每个数字,执行二进制搜索以找到数字 A(x),使得 A(n) + A(x) = R

      【讨论】:

      • 无需搜索 - 您可以比这更有效地进行搜索。
      • 在什么意义上有效?排序为 O(n log n),n 次二分查找的成本仅为 O(n log n)。因此,优化搜索毫无意义。你的意思是你的答案总体上比 O(n log n) 好?
      • 二分查找不是 O(log n) 吗?
      • 如果数组已经排序,你的算法是O(n log n),但是O(n)是可能的。
      • 确实,排序是O(n log n),但是通过从数组的每一端移入,搜索可以是O(n)。总体复杂度仍然是 O(n log n),但这并不意味着后一种解决方案严格来说不是更有效。
      【解决方案6】:

      如果您不介意将O(M) 花费在太空中,其中M 是您要寻找的总和,您可以在O(N + M) 时间内完成此操作。在i &lt;= M 单次通过N 时设置sums[i] = 1,然后在M/2 单次通过时检查(sums[i] &amp;&amp; sums[M-i])

      【讨论】:

        【解决方案7】:
        #include <iostream>
        using namespace std;
        
        #define MAX 15
        
        int main()
        {
         int array[MAX] = {-12,-6,-4,-2,0,1,2,4,6,7,8,12,13,20,24};
         const int find_sum = 0;
         int max_index = MAX - 1;
         int min_index = 0;
         while(min_index < max_index)
         {
          if(array[min_index] + array[max_index-min_index] == find_sum)
          {
           cout << array[min_index] << " & " << array[max_index-min_index] << " Matched" << endl;
           return 0;
          }
          if(array[min_index]+array[max_index-min_index] < find_sum)
          {
           min_index++;
           //max_index++;
          }
          if(array[min_index]+array[max_index-min_index] > find_sum)
          {
           max_index--;
          }
         }
         cout << "NO MATCH" << endl;
         return 0;
        }
        //-12 & 12 matched
        

        【讨论】:

          【解决方案8】:

          在 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
          

          【讨论】:

            【解决方案9】:

            我们可以使用 C++ STL map 来解决这个问题

            void subsetSum(int arr[], int n, int sum)
            {
                map<int, int>Map;
            
                for(int i=0; i<n; i++)
                {
                    Map[arr[i]]++;
                    if(Map.count(sum-arr[i]))
                    {
                        cout<<arr[i]<<" "<<sum-arr[i]<<"\n";
                    }
                }
            }
            

            【讨论】:

            • 使用 std::unordered_map 这是 HashMap 方法(无需自己设置哈希映射。)
            • 我认为增加 Map 元素的顺序并检查元素 [sum-arr[i]] 的计数是否必须颠倒以处理 arr[i] == sum - arr[i ] 案例。
            【解决方案10】:

            这是一个考虑重复条目的解决方案。它是用 javascript 编写的,并假定数组已排序。该解决方案在 O(n) 时间内运行,并且除了变量之外不使用任何额外的内存。

            var count_pairs = function(_arr,x) {
              if(!x) x = 0;
              var pairs = 0;
              var i = 0;
              var k = _arr.length-1;
              if((k+1)<2) return pairs;
              var halfX = x/2; 
              while(i<k) {
                var curK = _arr[k];
                var curI = _arr[i];
                var pairsThisLoop = 0;
                if(curK+curI==x) {
                  // if midpoint and equal find combinations
                  if(curK==curI) {
                    var comb = 1;
                    while(--k>=i) pairs+=(comb++);
                    break;
                  }
                  // count pair and k duplicates
                  pairsThisLoop++;
                  while(_arr[--k]==curK) pairsThisLoop++;
                  // add k side pairs to running total for every i side pair found
                  pairs+=pairsThisLoop;
                  while(_arr[++i]==curI) pairs+=pairsThisLoop;
                } else {
                  // if we are at a mid point
                  if(curK==curI) break;
                  var distK = Math.abs(halfX-curK);
                  var distI = Math.abs(halfX-curI);
                  if(distI > distK) while(_arr[++i]==curI);
                  else while(_arr[--k]==curK);
                }
              }
              return pairs;
            }
            

            所以这里适合所有人。

            从数组的两侧开始,慢慢地向内工作,确保计算重复项(如果存在)。

            它只计算对,但可以重做

            • 找到配对
            • 找对
            • 找到对 > x

            如果它是最佳答案,请享受并且不要忘记碰撞它!

            【讨论】:

              【解决方案11】:

              考虑重复并仅使用每个数字一次的解决方案:

              void printPairs(int[] numbers, int S) {
                  // toMap(numbers) converts the numbers array to a map, where
                  // Key is a number from the original array
                  // Value is a count of occurrences of this number in the array
                  Map<Integer, Integer> numbersMap = toMap(numbers); 
              
                  for (Entry<Integer, Integer> entry : numbersMap.entrySet()) {
                    if (entry.getValue().equals(0)) {
                      continue;
                    }
                    int number = entry.getKey();
                    int complement = S - number;
                    if (numbersMap.containsKey(complement) && numbersMap.get(complement) > 0) {
                    for (int j = 0; j < min(numbersMap.get(number), 
                                            numbersMap.get(complement)); j++) {
                      if (number.equals(complement) && numbersMap.get(number) < 2) {
                         break;
                      }
                      System.out.println(number, complement);
                      numbersMap.put(number, numbersMap.get(number) - 1);
                      numbersMap.put(complement, numbersMap.get(complement) - 1);
                    }
                  }
                }
              }
              

              【讨论】:

                【解决方案12】:

                Hashtable 解决方案,用 Ruby 编写(非常容易理解):

                value = 100
                pairs = [1,99,5,95]
                hash_of_pairs = {}
                
                pairs.map! do |pair|
                  # Adds to hashtable the pair
                  hash_of_pairs[pair] = pair
                
                  # Finds the value the pair needs
                  new_pair = hash_of_pairs[value - pair]
                
                  # Returns the pair whenever the pair exists, or nil
                  [new_pair, pair] if !new_pair.nil?
                end.compact! # Cleans up the array, removing all nil values
                
                print pairs # [[1,99], [5,95]]
                

                【讨论】:

                  【解决方案13】:
                  @Test
                  public void hasPairWithSum() {
                    assertFalse(hasPairWithSum_Ordered_Logarithmic(new int[] { 1, 2, 3, 9 }, 8));
                    assertTrue(hasPairWithSum_Ordered_Logarithmic(new int[] { 1, 2, 4, 4 }, 8));
                  
                    assertFalse(hasPairWithSum_Ordered_Linear(new int[] { 1, 2, 3, 9 }, 8));
                    assertTrue(hasPairWithSum_Ordered_Linear(new int[] { 1, 2, 4, 4 }, 8));
                  
                    assertFalse(hasPairWithSum_Unsorted_Linear(new int[] { 9, 1, 3, 2 }, 8));
                    assertTrue(hasPairWithSum_Unsorted_Linear(new int[] { 4, 2, 1, 4 }, 8));
                  
                    assertFalse(hasPairWithSum_Unsorted_Quadratic(new int[] { 9, 1, 3, 2 }, 8));
                    assertTrue(hasPairWithSum_Unsorted_Quadratic(new int[] { 4, 2, 1, 4 }, 8));
                  }
                  
                  private boolean hasPairWithSum_Ordered_Logarithmic(int[] data, int sum) {
                    for (int i = 0; i < data.length; i++) {
                      int current = data[i];
                      int complement = sum - current;
                      int foundIndex = Arrays.binarySearch(data, complement);
                      if (foundIndex >= 0 && foundIndex != i) {
                        return true;
                      }
                    }
                    return false;
                  }
                  
                  private boolean hasPairWithSum_Ordered_Linear(int[] data, int sum) {
                    int low = 0;
                    int high = data.length - 1;
                    while (low < high) {
                      int total = data[low] + data[high];
                      if (total == sum) {
                        return true;
                      } else if (total < sum) {
                        low++;
                      } else {
                        high--;
                      }
                    }
                    return false;
                  }
                  
                  private boolean hasPairWithSum_Unsorted_Linear(int[] data, int sum) {
                    Set<Integer> complements = Sets.newHashSet();
                    for (int current : data) {
                      if (complements.contains(current)) {
                        return true;
                      }
                      complements.add(sum - current);
                    }
                    return false;
                  }
                  
                  private boolean hasPairWithSum_Unsorted_Quadratic(int[] data, int sum) {
                    for (int i = 0; i < data.length; i++) {
                      int current = data[i];
                      int complement = sum - current;
                      for (int j = 0; j < data.length; j++) {
                        if (data[j] == complement && i != j) {
                          return true;
                        }
                      }
                    }
                    return false;
                  }
                  

                  【讨论】:

                    【解决方案14】:

                    创建一个哈希表,然后在其中查找值。

                    function sum_exist(num : number, arr : any[]) {
                        var number_seen = {};
                        for(let item of arr){
                            if(num - item in number_seen){
                                return true
                            }
                            number_seen[item] = 0;
                        }
                        return false;
                    }
                    

                    测试用例(使用 Jest)

                    test('Given a list of numbers, return whether any two sums equal to the set number.', () => {
                        expect(sum_exist(17 , [10, 15, 3, 7])).toEqual(true);
                    });
                    
                    
                    test('Given a list of numbers, return whether any two sums equal to the set number.', () => {
                        expect(sum_exist(16 , [10, 15, 3, 7])).toEqual(false);
                    });
                    

                    【讨论】:

                      【解决方案15】:
                      #python 3.x
                      def sum_pairs(list_data, number):    
                          list_data.sort()
                          left = 0
                          right = len(list_data)-1
                          pairs = []
                          while left < right:        
                              if list_data[left]+list_data[right] == number:
                                  find_pairs = [list_data[left], list_data[right]]
                                  pairs.append(find_pairs)
                                  right = right-1
                              elif list_data[left]+list_data[right] < number:
                                  left = left+1
                              else:
                                  right = right-1
                          return pairs
                      

                      【讨论】:

                        猜你喜欢
                        • 2020-02-22
                        • 1970-01-01
                        • 2015-12-17
                        • 1970-01-01
                        • 1970-01-01
                        • 2014-07-25
                        • 2015-02-26
                        • 2015-09-18
                        • 2011-10-07
                        相关资源
                        最近更新 更多