【问题标题】:Linear time algorithm for 2-SUM2-SUM 的线性时间算法
【发布时间】:2012-08-09 07:34:32
【问题描述】:

给定一个整数 x 和一个由 N 个不同整数组成的排序数组 a,设计一个线性时间算法来确定是否存在两个不同的索引 i 和 j 使得 a[i] + a[j] == x

【问题讨论】:

  • 如果它是不同的但没有排序怎么办?如果不区分且未排序怎么办??

标签: algorithm


【解决方案1】:

这是Subset sum problem的类型

这是我的解决方案。不知道是不是早知道。想象一下两个变量 i 和 j 的函数的 3D 图:

sum(i,j) = a[i]+a[j]

对于每个i,都有这样一个ja[i]+a[j] 最接近x。所有这些 (i,j) 对形成 closest-to-x 行。我们只需要沿着这条线走,寻找a[i]+a[j] == x

 int i = 0; 
 int j = lower_bound(a.begin(), a.end(), x)  -  a.begin(); 
 while (j >= 0 && j < a.size()  &&  i < a.size())  {  
    int sum = a[i]+a[j]; 
    if (sum == x)   { 
        cout << "found: " << i << " " << j << endl;  
        return;
    }
    if (sum > x)    j--;
    else            i++;
    if (i > j) break;
 }  
 cout << " not found\n";

复杂度:O(n)

【讨论】:

  • 这也有效,而且效率更高(由于数组的排序性质)。
  • 太棒了!但是负值呢?删除lower_bound 调用并设置int j = a.size() - 1 可能是一个解决方案?
  • @facho - 它应该适用于负值。如果没有 lower_bound,它会起作用,但效率会降低。
  • 鉴于这是一个 code-mostly 答案,您可能想提及一种编程语言,甚至评论您的代码(它是什么(/_everything_)那里/假设去完成?)。我认为@fcatho 的反对意见非常有效:检查lower_bound(a.begin(), a.end(), x-*a)。为什么不控制循环只是while (i &lt; j)?另一种方法是使用lower_bound(a.begin(), a.end(), x/2) 并由内而外工作 - 更复杂的循环控制。 (只需向下滚动到其他答案 - 这是Guru Devanla's。)
  • 我不遵循“最近”的想法,2点之间的距离?
【解决方案2】:

从补语的角度思考。

遍历列表,为每个项目计算出该数字到达 X 所需的数字是多少。将数字和补码放入哈希中。在迭代检查以查看数字或其补码是否在散列中。如果有,找到了。

编辑:因为我有一些时间,一些伪代码。

boolean find(int[] array, int x) {
    HashSet<Integer> s = new HashSet<Integer>();

    for(int i = 0; i < array.length; i++) {
        if (s.contains(array[i]) || s.contains(x-array[i])) {
            return true;
        }
        s.add(array[i]);
        s.add(x-array[i]);
    }
    return false;
}

【讨论】:

  • 在最坏情况下创建哈希图是 O(N^2)。
  • Leonid,同意,但一般来说,对于采访 Q,他们似乎希望假设哈希表是 O(1)(尽管我猜你会因为知道它可以转移而获得额外的分数)。话虽如此,一旦您了解了哈希表版本,由于排序的性质,您的解决方案自然会遵循(尽管不一定很明显,必须很好地理解约束才能实现)作为一种简单的优化。
  • 似乎 C++ 中的 map 通常不能用作常规哈希表?对于常规哈希表,时间复杂度是 O(1)O(n) 而它是 O(lgn) 对于 insert find 在 C++ 中。 “复杂性(插入)如果插入单个元素,通常大小为对数,但如果给出提示并且给出的位置是最佳的,则摊销常数。” link
  • @zhenjie std::map 通常在红黑树中实现。
  • 你确定 s.add(array[i]);需要吗?
【解决方案3】:
  1. 首先通过搜索> ceil(x/2) 的第一个值。让我们将此值称为 L。
  2. 从 L 的索引开始,向后搜索,直到找到与总和匹配的另一个操作数。

是 2*n ~ O(n)

这我们可以扩展到二分搜索。

  1. 使用二分搜索搜索一个元素,这样我们就可以找到 L,使得 L 为 min(elements in a > ceil(x/2))。

  2. 对 R 执行相同操作,但现在将 L 作为数组中可搜索元素的最大大小。

这种方法是 2*log(n)。

【讨论】:

  • 这可能很棒,但是你能告诉我更多关于 R 是什么的细节吗?
  • 对于二分查找 L - 左,R - 右;就像子集的左右边界一样。
  • 请注意,如果我们允许数组中有重复的数字,这将不起作用(就像在采访中有时会看到的那样)。例如 a=[13,13,22] 和 x=26。你会得到值 22 来匹配第一次搜索 (L=2) 并且永远找不到 R
【解决方案4】:

这是一个使用 Dictionary 数据结构和数字补码的 python 版本。这具有线性运行时间(N 阶:O(N)):

def twoSum(N, x):
    dict = {}

    for i in range(len(N)):
        complement = x - N[i]
        if complement in dict:
            return True
        dict[N[i]] = i

    return False

# Test
print twoSum([2, 7, 11, 15], 9) # True
print twoSum([2, 7, 11, 15], 3) # False

【讨论】:

  • 你确定这行得通吗? dict 应该在 for 循环之前填充
  • 您使用集合中的 defaultdict 而不是填充 dict
【解决方案5】:

鉴于数组已排序(WLOG 降序排列),我们可以执行以下操作: 算法 A_1: 我们得到 (a_1,...,a_n,m), a_1<... m unsat sat>

很明显,这是 O(n),因为计算的和的最大数量正好是 n。正确性的证明留作练习。

这只是用于 SUBSET-SUM 的 Horowitz 和 Sahni (1974) 算法的一个子程序。 (但请注意,几乎所有通用 SS 算法都包含这样的例程,Schroeppel, Shamir (1981), Howgrave-Graham_Joux (2010), Becker-Joux (2011)。)

如果给定一个无序列表,实现这个算法将是 O(nlogn),因为我们可以使用 Mergesort 对列表进行排序,然后应用 A_1。

【讨论】:

    【解决方案6】:

    遍历数组并将合格的数字及其索引保存到地图中。该算法的时间复杂度为O(n)。

    vector<int> twoSum(vector<int> &numbers, int target) {
        map<int, int> summap;
        vector<int> result;
        for (int i = 0; i < numbers.size(); i++) {
            summap[numbers[i]] = i;
        }
        for (int i = 0; i < numbers.size(); i++) {
            int searched = target - numbers[i];
            if (summap.find(searched) != summap.end()) {
                result.push_back(i + 1);
                result.push_back(summap[searched] + 1);
                break;
            }
        }
        return result;
    }
    

    【讨论】:

    • summap.find() 是否会遍历地图中的所有元素,这会使复杂度增加?
    • summap.find的复杂度是O(log n),所以这个算法的复杂度是O(n log n),而不是O(n)。
    【解决方案7】:

    我会像这样将差异添加到HashSet&lt;T&gt;

    public static bool Find(int[] array, int toReach)
    {
        HashSet<int> hashSet = new HashSet<int>();
    
        foreach (int current in array)
        {
            if (hashSet.Contains(current))
            {
                return true;
            }
            hashSet.Add(toReach - current);
        }
        return false;
    }
    

    【讨论】:

      【解决方案8】:

      注意:代码是我的,但测试文件不是。另外,哈希函数的这个想法来自网上的各种阅读。

      Scala 中的实现。它对值使用 hashMap 和自定义(但简单)映射。我同意它没有利用初始数组的排序特性。

      哈希函数

      我通过将每个值除以 10000 来固定存储桶大小。该数字可能会有所不同,具体取决于您想要的存储桶大小,可以根据输入范围进行优化。

      例如,键 1 负责从 1 到 9 的所有整数。

      对搜索范围的影响

      这意味着,对于当前值 n,您正在为其寻找补码 c,例如 n + c = x x 是您试图找到 2-SUM 的元素),只有 3 个可能的桶可以补码:

      • -键
      • -key + 1
      • -key - 1

      假设您的号码在以下格式的文件中:

      0
      1
      10
      10
      -10
      10000
      -10000
      10001
      9999
      -10001
      -9999
      10000
      5000
      5000
      -5000
      -1
      1000
      2000
      -1000
      -2000
      

      这是 Scala 中的实现

      import scala.collection.mutable                                                                                                                                                                       
      import scala.io.Source
      
      object TwoSumRed {
        val usage = """ 
          Usage: scala TwoSumRed.scala [filename]
        """
      
        def main(args: Array[String]) {
          val carte = createMap(args) match {
            case None => return
            case Some(m) => m
          }
      
          var t: Int = 1
      
          carte.foreach {
            case (bucket, values) => {
              var toCheck: Array[Long] = Array[Long]() 
      
              if (carte.contains(-bucket)) {
                toCheck = toCheck ++: carte(-bucket)
              }
              if (carte.contains(-bucket - 1)) {
                toCheck = toCheck ++: carte(-bucket - 1)
              }
              if (carte.contains(-bucket + 1)) {
                toCheck = toCheck ++: carte(-bucket + 1)
              }
      
              values.foreach { v =>
                toCheck.foreach { c =>
                  if ((c + v) == t) {
                    println(s"$c and $v forms a 2-sum for $t")
                    return
                  }
                }
              }
            }
          }
        }
      
        def createMap(args: Array[String]): Option[mutable.HashMap[Int, Array[Long]]] = {
          var carte: mutable.HashMap[Int,Array[Long]] = mutable.HashMap[Int,Array[Long]]()
      
          if (args.length == 1) {
            val filename = args.toList(0)
            val lines: List[Long] = Source.fromFile(filename).getLines().map(_.toLong).toList
            lines.foreach { l =>
              val idx: Int = math.floor(l / 10000).toInt
              if (carte.contains(idx)) {
                carte(idx) = carte(idx) :+ l
              } else {
                carte += (idx -> Array[Long](l))
              }
            }
            Some(carte)
          } else {
            println(usage)
            None
          }
        }
      }
      

      【讨论】:

        【解决方案9】:
        int[] b = new int[N];
        for (int i = 0; i < N; i++)
        {
            b[i] = x - a[N -1 - i];
        }
        for (int i = 0, j = 0; i < N && j < N;)
            if(a[i] == b[j])
            {
               cout << "found";
               return;
             } else if(a[i] < b[j])
                i++;
             else
                j++;
        cout << "not found";
        

        【讨论】:

        • ,你想出了一个非常好的算法。 a[i] == b[j] 和索引 i 等于索引 j 的情况如何?谢谢。
        【解决方案10】:

        这里是一个线性时间复杂度解 O(n) 时间 O(1) 空间

         public void twoSum(int[] arr){
        
           if(arr.length < 2) return;
        
           int max = arr[0] + arr[1];
           int bigger = Math.max(arr[0], arr[1]);
           int smaller = Math.min(arr[0], arr[1]);
           int biggerIndex = 0;
           int smallerIndex = 0;
        
            for(int i = 2 ; i < arr.length ; i++){
        
                if(arr[i] + bigger <= max){ continue;}
                else{
                    if(arr[i] > bigger){
                        smaller = bigger;
                        bigger = arr[i];
                        biggerIndex = i;
                    }else if(arr[i] > smaller)
                    {
                        smaller = arr[i];
                        smallerIndex = i;
                    }
                    max = bigger + smaller;
                }
        
            }
        
            System.out.println("Biggest sum is: " + max + "with indices ["+biggerIndex+","+smallerIndex+"]");
        

        }

        【讨论】:

          【解决方案11】:

          解决方案

          • 我们需要数组来存储索引
          • 检查数组是否为空或包含的元素少于 2 个
          • 定义数组的起点和终点
          • 迭代直到满足条件
          • 检查总和是否等于目标。如果是,请获取索引。
          • 如果不满足条件,则根据总和值向左或向右遍历
          • 向右移动
          • 向左移动

          更多信息:[http://www.prathapkudupublog.com/2017/05/two-sum-ii-input-array-is-sorted.html

          【讨论】:

            【解决方案12】:

            感谢狮子座

            他的java解决方案,如果你想试一试

            我删除了返回,所以如果数组已排序,但允许重复,它仍然给出对

            static boolean cpp(int[] a, int x) {
                int i = 0;
                int j = a.length - 1;
                while (j >= 0 && j < a.length && i < a.length) {
                    int sum = a[i] + a[j];
                    if (sum == x) {
                    System.out.printf("found %s, %s \n", i, j);
            //                return true;
                    }
                    if (sum > x) j--;
                    else i++;
                    if (i > j) break;
                }
                System.out.println("not found");
                return false;
                }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-02-05
              • 1970-01-01
              • 2013-05-29
              • 2011-05-02
              • 2018-08-29
              • 1970-01-01
              相关资源
              最近更新 更多