【问题标题】:O(n log(n)) algorithm that checks if sum of 2 numbers in a int[] = given numberO(n log(n)) 算法检查 int[] 中 2 个数字的总和是否 = 给定数字
【发布时间】:2011-12-28 12:39:14
【问题描述】:

我应该创建一个O(n log(n)) 算法来检查 int[] 中两个数字的总和是否 == 给定数字。

例如。给定 [1,4,7,2,3,4] 总和将是 8 (1+7) 但不是 20

给出的答案建议使用二进制排序或合并排序,但他们只是给出了合并排序算法,而没有逻辑处理这个特殊要求。然后另一个答案是:

假设 x 是我们要检查的总和,z 是 这个数组:下面的算法解决了这个问题:

  1. 对 S 中的元素进行排序。
  2. 形成集合 S' = {z : z = x − y for some y ∈ S}。
  3. 对 S' 中的元素进行排序。
  4. 如果 S 中的任何值出现多次,则删除除一个之外的所有实例。对 S' 执行相同操作。
  5. 合并两个排序集 S 和 S'。
  6. 当且仅当相同的值出现在合并输出中的连续位置时,S 中存在两个元素的和正好为 x。

为了证明第 4 步中的主张的合理性,首先观察如果有任何值 在合并输出中出现两次,必须连续出现 职位。因此,我们可以重述步骤 5 中的条件,因为存在 S 中的两个元素的和正好是 x 当且仅当相同的值 在合并的输出中出现两次。假设某个值 w 出现 两次。然后 w 在 S 中出现一次,在 S' 中出现一次。因为 w 出现在 S',存在一些 y ∈ S 使得 w = x - y,或 x = w + y。由于 w ∈ S,元素 w 和 y 在 S 中,总和为 x。

相反,假设有值 w, y ∈ S 使得 w + y = X。然后,由于 x - y = w,值 w 出现在 S' 中。因此,w 在 S 和 S',因此它会在合并的输出中出现两次。

第 1 步和第 3 步需要 O(n log n) 步。步骤 2、4、5 和 6 需要 O(n) 步。因此总体运行时间为 O(n log n)。

但我真的不明白他们的意思。在第 2 步中,x 和 y 是什么?

但是下面是我自己创建的,不知道是不是O(n log(n))

class FindSum {

  public static void main(String[] args) {
    int[] arr = {6,1,2,3,7,12,10,10};
    int targetSum = 20;

    Arrays.sort(arr);
    System.out.println(Arrays.toString(arr));
    int end = arr.length - 1;
    if (FindSum.binarySearchSum(arr, targetSum, 0, end, 0, end)) {
      System.out.println("Found!");
    } else {
      System.out.println("Not Found :(");
    }
  } 

  public static boolean binarySearchSum(int[] arr, int targetSum,
                                        int from1, int end1,
                                        int from2, int end2) {
    // idea is to use 2 "pointers" (simulating 2 arrays) to (binary) search 
    // for target sum
    int curr1 = from1 + (end1-from1)/2;
    int curr2 = from2 + (end2-from2)/2;
    System.out.print(String.format("Looking between %d to %d, %d to %d: %d, %d", from1, end1, from2, end2, curr1, curr2));
    int currSum = arr[curr1] + arr[curr2];
    System.out.println(". Sum = " + currSum);

    if (currSum == targetSum) { 
      // base case
      return true;
    } else if (currSum > targetSum) { 
      // currSum more than targetSum
      if (from2 != end2) { 
        // search in lower half of 2nd "array"
        return FindSum.binarySearchSum(arr, targetSum, from1, end1, from2, curr2 - 1);
      } else if (from1 != end2) { 
        // search in lower half of 1st "array" (resetting the start2, end2 args)
        return FindSum.binarySearchSum(arr, targetSum, from1, curr1 - 1, 0, arr.length - 1);
      } else {
        // can't find
        return false;
      }
    } else {
      // currSum < targetSum
      if (from2 != end2) { 
        // search in upper half of 2nd "array"
        return FindSum.binarySearchSum(arr, targetSum, from1, end1, curr2 + 1, end2);
      } else if (from1 != end2) { 
        // search in upper half of 1st "array" (resetting the start2, end2 args)
        return FindSum.binarySearchSum(arr, targetSum, curr1 + 1, end1, 0, arr.length - 1);
      } else {
        // can't find
        return false;
      }
    }
  }

}

【问题讨论】:

  • 解决简单任务的复杂方式。算法描述中的那个。
  • 3.对 [集合] S' 中的元素进行排序。啊。无法对集合进行排序。

标签: java big-o binary-search


【解决方案1】:

类似于@user384706,但是您可以在O(n) 中执行此操作。

他们说的是以下内容: S=[1,4,7,2,3,4]

将这些添加到 HashSet,理想情况下为 TIntHashSet(但时间复杂度相同)

int total = 9;
Integer[] S = {1, 4, 7, 2, 3, 4, 6};
Set<Integer> set = new HashSet<Integer>(Arrays.asList(S));
for (int i : set)
    if (set.contains(total - i))
        System.out.println(i + " + " + (total - i) + " = " + total);

打印

2 + 7 = 9
3 + 6 = 9
6 + 3 = 9
7 + 2 = 9

【讨论】:

  • 你怎么知道这是 O(n)?如果这是在硬件中实现的,则您必须假设哈希访问的最坏情况是 O(n),因为所有哈希条目都可能发生冲突,即位于同一位置。 SW 是否获得免费通行证来假设哈希访问始终为 O(1)?
  • @JamesTesta 在这种情况下,每个元素都有一个不同的桶。您可以假设 O(1) 查找或以合理的使用模式接近它。这种假设失效的地方是拒绝服务攻击,但通常这不是问题。
【解决方案2】:

他们说的是:
S=[1,4,7,2,3,4]

使用合并排序对 S 进行排序,得到 Ss=[1,2,3,4,7]。费用为O(nlogn) - 只需查看 wiki 即可。

那么你有x=8
所以你通过用S 中的元素减去x 来形成S'=[7,6,5,4,1]
O(nlogn) 中使用合并排序对S' 进行排序
删除重复项需要O(n)

然后你合并SsS'
您检查 O(n) 中连续位置的重复项。
总数为:
O(nlogn)+O(nlogn)+O(n)+O(n)+O(n) = O(nlogn)

【讨论】:

  • @soulcheck:我只是在解释他作为模板得到了什么。我认为 OP 甚至不知道什么是合并排序。
【解决方案3】:

O(n) 解决方案怎么样?

您的问题并不清楚您是否应该使用您所描述的“另一个答案”[原文如此],或者您是否可以提出自己的解决方案。

您应该问的第一件事是“有什么要求?”因为有限制。您将收到的最大整数数是多少?两百万?一千万?这些整数的范围是多少?在您的问题中,它们似乎总是大于零。这些整数可以具有的最大值是多少?你可以使用多少内存?

因为有总是权衡。

例如,这是针对您的问题的非优化(见下文)O(n) 解决方案(我在您的输入中添加了“8”):

@Test
public void testIt() {
    final int max = 10000000;
    final int[] S = new int[max+1];
    final int[] in = { 1, 4, 3, 2, 4, 7, 8 };
    for ( final int i : in ) {
        S[i]++;
    }
    assertFalse( containsSum(S, 1) );
    assertFalse( containsSum(S, 2) );
    assertTrue( containsSum(S, 3) );
    assertTrue( containsSum(S, 4) );
    assertTrue( containsSum(S, 5) );
    assertTrue( containsSum(S, 6) );
    assertTrue( containsSum(S, 7) );
    assertTrue( containsSum(S, 8) );
    assertTrue( containsSum(S, 9) );
    assertTrue( containsSum(S, 10) );
    assertTrue( containsSum(S, 11) );
    assertFalse( containsSum(S, 13) );
    assertFalse( containsSum(S, 14) );
    assertTrue( containsSum(S, 12) );
    assertTrue( containsSum(S, 15) );
    assertFalse( containsSum(S, 16) );
}

private static boolean containsSum( final int[] ar, final int sum ) {
    boolean found = false;
    for (int i = 1; i < sum && !found; i++) {
            final int b = sum - i;
            found = i == b ? ar[i] > 1 : ar[i] > 0 && ar[b] > 0;
    }
    return found;
}

它没有经过优化,因为它很容易编写一个O(n),使用“仅”1 GB 的内存(你代表你的 S而你的 S' 使用位而不是整数,就像我在这里所做的那样)。

当然,有人可能会认为“但是 1GB 是很多内存”:但这完全取决于要求。我上面的解决方案(或其优化版本)是 O(n) 并且可以立即解决由 1 亿个整数组成的输入,而任何其他解决方案都会失败(因为你' d 由于 Java 对象的开销而出现 OutOfMemory 错误)。

首先要问的是“有什么要求?”。您需要有关输入的更多信息,因为它始终是一种权衡。

【讨论】:

  • 如果没有提到任何假设,那么它应该被视为“对输入数据没有任何额外的假设”。
  • 很难相信有人确实对此投了反对票...“没有对输入数据的任何额外假设” 没有任何意义:我们受限于我们的限制计算设备。我们是否应该使用 BigInteger 因为整数可以真的 大?但是那么我们可以有多少个整数呢?当使用这种方法可以解决不使用 BigInteger 的任何其他解决方案都会失败的问题时,对此投反对票真的很奇怪......
  • 是的,这是我(我承认,太快了)的否决票,除非您编辑答案,否则我无法取消它,所以只需进行 noop 编辑,我会收回它。跨度>
  • 此外,一旦输入数组已经在内存中,您就可以就地执行此测试,那么您提到的 OutOfMemory 错误在哪里?
  • 这个算法让人们看到了一种不同的思考问题的方式,非常感谢和赞成:)
【解决方案4】:

你的算法是 O(n log n)。每次您将第一个数组大小除以 2 或对第二个数组进行二进制搜索。这是 O((log n) ^2) 最坏的情况(即 S = {1,2...,n} 和 x = 0),因此它被排序吸收了。

无论如何,您可以通过以下方式在 O(n log n) 中更轻松地做到这一点:

  1. 对数组进行排序 (O(n log n))
  2. 迭代它的元素 (O(n)),同时在每次迭代中对当前元素的 X 补码进行二进制搜索 (O(log n))。

编辑: 回答你的第一个问题。 x 是您要查找的总和,y 是输入集的元素。所以如果 S= {y_1, y_2, y_3,..., y_n} 那么 S' = {x - y_1, x - y_2, x - y_3, ...x - y_n}

【讨论】:

  • 为什么要投反对票?最坏情况分析和算法都ok!
【解决方案5】:

它的工作原理如下:

  1. 对输入数组进行排序
  2. 创建两个变量 front=0 [start point] back=length of array [end point]。
  3. 如果前后均为零增量,则从数组的两端开始计算其和
  4. 如果不增加包含更大元素的一侧。[由于数组已排序,因此没有机会找到总和为 0 的元素]
  5. 在前面 >= 后面时停止。

public class TwoSumFaster {

private static int countTwoSum(int[] numbers) {
    int count = 0;
    int front = 0, rear = numbers.length - 1;

    while (front < rear) {
        if (numbers[front] + numbers[rear] == 0) {
            front++;
            rear--;
            count++;
        } else {
            if (Math.abs(numbers[front]) > Math.abs(numbers[rear])) {
                front++;
            } else {
                rear--;
            }
        }
    }

    return count;
}

public static void main(String[] args) {
    int[] numbers = { 1, 3, 5, 7, 12, 16, 19, 15, 11, 8, -1, -3, -7, -8, -11, -17, -15 };
    Arrays.sort(numbers); 
    System.out.println(countTwoSum(numbers));
}

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-15
    相关资源
    最近更新 更多