编程面试问题/如何查找数组中的任何两个整数是否为零?

时间:2010-06-29 08:31:09

标签: algorithm

不是一个家庭作业问题,而是一个可能的面试问题......

  1. 给定一个整数数组,写一个算法,检查任何两个的总和是否为零。
  2. 此解决方案的大O是什么?
  3. 寻找非暴力方法

9 个答案:

答案 0 :(得分:10)

使用查找表:扫描数组,将所有正值插入表中。如果遇到相同幅度的负值(可以在表格中轻松查找);它们的总和将为零。查找表可以是用于节省内存的哈希表。

此解决方案应为O(N)。

伪代码:

var table = new HashSet<int>();
var array = // your int array
foreach(int n in array)
{
     if ( !table.Contains(n) ) 
         table.Add(n);
     if ( table.Contains(n*-1) )
         // You found it.;
}

答案 1 :(得分:8)

其他人提到的哈希表解决方案通常是O(n),但理论上也可以退化为O(n^2)

这是一个永不退化的Theta(n log n)解决方案:

Sort the array (optimal quicksort, heap sort, merge sort are all Theta(n log n))
for i = 1, array.len - 1
    binary search for -array[i] in i+1, array.len

如果您的二进制搜索返回true,那么您可以停止算法并获得解决方案。

答案 2 :(得分:3)

O(n log n)解决方案(即排序)将对所有数据值进行排序,然后在从最高到最低运行指针的同时运行从最低到最高的指针:

def findmatch(array n):
    lo = first_index_of(n)
    hi = last_index_of(n)
    while true:
        if lo >= hi:                   # Catch where pointers have met.
            return false
        if n[lo] = -n[hi]:             # Catch the match.
            return true
        if sign(n[lo]) = sign(n[hi]):  # Catch where pointers are now same sign.
            return false
        if -n[lo] > n[hi]:             # Move relevant pointer.
            lo = lo + 1
        else:
            hi = hi - 1

O(n)时间复杂度解决方案是维护满足所有值的数组:

def findmatch(array n):
    maxval = maximum_value_in(n)         # This is O(n).
    array b = new array(0..maxval)       # This is O(1).
    zero_all(b)                          # This is O(n).
    for i in index(n):                   # This is O(n).
        if n[i] = 0:
            if b[0] = 1:
                return true
            b[0] = 1
            nextfor

        if n[i] < 0:
            if -n[i] <= maxval:
                if b[-n[i]] = 1:
                    return true;
                b[-n[i]] = -1
            nextfor

        if b[n[i]] = -1:
            return true;
        b[n[i]] = 1

这可以通过简单地维持给定幅度的符号来实现,每个可能的幅度在0和最大值之间。

因此,如果在任何时候我们找到-12,我们将b [12]设置为-1。然后,如果我们找到12,我们知道我们有一对。除了我们将符号设置为1之外,首先找到正数。如果我们连续找到两个-12,那仍然将b [12]设置为-1,等待12来抵消它。

此代码中唯一的特殊情况是:

  • 0被特别处理,因为我们需要检测它,尽管它在这个算法中有些奇怪的特性(我特别对待它以免使正面和负面情况复杂化)。
  • 可以安全地忽略其大小高于最高正值的低负值,因为不可能匹配。

与最棘手的“最小化时间复杂度”算法一样,这个算法有一个权衡,因为它可能具有更高的空间复杂度(例如当阵列中只有一个元素恰好是20亿个时)。

在这种情况下,您可能会恢复到排序O(n log n)解决方案,但是,如果您事先知道限制(例如,如果您将整数限制在范围[-100,100]),可以是一个强大的优化。


回想起来,或许看起来更清洁的解决方案可能是:

def findmatch(array num):
    # Array empty means no match possible.
    if num.size = 0:
        return false

    # Find biggest value, no match possible if empty.
    max_positive = num[0]
    for i = 1 to num.size - 1:
        if num[i] > max_positive:
            max_positive = num[i]
    if max_positive < 0:
        return false

    # Create and init array of positives.
    array found = new array[max_positive+1]
    for i = 1 to found.size - 1:
        found[i] = false
    zero_found = false

    # Check every value.
    for i = 0 to num.size - 1:
        # More than one zero means match is found.
        if num[i] = 0:
            if zero_found:
                return true
            zero_found = true

        # Otherwise store fact that you found positive.
        if num[i] > 0:
            found[num[i]] = true

    # Check every value again.
    for i = 0 to num.size - 1:
        # If negative and within positive range and positive was found, it's a match.
        if num[i] < 0 and -num[i] <= max_positive:
            if found[-num[i]]:
                return true

    # No matches found, return false.
    return false

这使得一次完整传递和一次部分传递(或完全没有匹配),而原始只进行部分传递,但我认为它更容易阅读,每个数字只需要一位(找到正数或未找到)而不是两个(无,正面或负面发现)。无论如何,它的O(n)时间复杂度仍然非常高。

答案 3 :(得分:1)

我认为IVlad的答案可能就是你所追求的,但这里的方法略显偏离。

如果整数可能很小并且内存不是约束,那么您可以使用BitArray集合。这是System.Collections中的.NET类,尽管Microsoft的C ++具有等效的bitset

BitArray类分配一块内存,并用零填充它。然后,您可以在指定的索引处“获取”和“设置”位,因此您可以调用myBitArray.Set(18, true),这将在内存块中设置索引18处的位(然后读取类似于00000000,00000000,00100000的内容) 。设置位的操作是O(1)操作。

因此,假设一个32位整数范围和1Gb备用内存,您可以采用以下方法:

BitArray myPositives = new BitArray(int.MaxValue);
BitArray myNegatives = new BitArray(int.MaxValue);
bool pairIsFound = false;
for each (int testValue in arrayOfIntegers)
{
    if (testValue < 0)
    {
        // -ve number - have we seen the +ve yet?
        if (myPositives.get(-testValue))
        {
            pairIsFound = true;
            break;
        }
        // Not seen the +ve, so log that we've seen the -ve.
        myNegatives.set(-testValue, true);
    }
    else
    {
        // +ve number (inc. zero). Have we seen the -ve yet?
        if (myNegatives.get(testValue))
        {
            pairIsFound = true;
            break;
        }
        // Not seen the -ve, so log that we've seen the +ve.
        myPositives.set(testValue, true);
        if (testValue == 0)
        {
            myNegatives.set(0, true);
        }
    }
}

// query setting of pairIsFound to see if a pair totals to zero.

现在我不是统计学家,但我认为这是一个O(n)算法。不需要排序,最长持续时间的情况是没有对存在且整个整数数组被迭代。

嗯 - 这是不同的,但我认为这是目前为止发布的最快的解决方案。

评论

答案 4 :(得分:0)

也许将每个数字都放在一个哈希表中,如果你看到一个负数检查一个冲突?上)。你确定问题不是要找出数组中的任何元素是否等于0?

答案 5 :(得分:0)

给定一个排序数组,您可以使用两个指针找到数字对(-n和+ n):

  • 第一个指针向前移动(在负数上),
  • 第二个指针向后移动(在正数上),
  • 取决于指针指向的值,移动其中一个指针(绝对值较大的指针)
  • 一旦指针符合或一个0
  • 就停止
  • 相同的值(一个为负,一个为正或两个为空)是匹配。

现在,这是O(n),但排序(如果需要)是O(n * log(n))。

编辑:示例代码(C#)

// sorted array
var numbers = new[]
{
    -5, -3, -1, 0, 0, 0, 1, 2, 4, 5, 7, 10 , 12
};

var npointer = 0;   // pointer to negative numbers
var ppointer = numbers.Length - 1;  // pointer to positive numbers

while( npointer < ppointer )
{
    var nnumber = numbers[npointer];
    var pnumber = numbers[ppointer];

    // each pointer scans only its number range (neg or pos)
    if( nnumber > 0 || pnumber < 0 )
    {
        break;
    }

    // Do we have a match?
    if( nnumber + pnumber == 0 )
    {
        Debug.WriteLine( nnumber + " + " + pnumber );
    }

    // Adjust one pointer
    if( -nnumber > pnumber )
    {
        npointer++;
    }
    else
    {
        ppointer--;
    }
}

有趣的是:我们在数组中有0, 0, 0。该算法将输出两对。但实际上有三对......我们需要更多的规范才能确切输出。

答案 6 :(得分:0)

这是一个很好的数学方法:记住所有素数(即构造一个数组prime[0 .. max(array)],其中n是输入数组的长度,因此prime[i]代表i - 素数。

counter = 1
for i in inputarray:
    if (i >= 0):
        counter = counter * prime[i]
for i in inputarray:
    if (i <= 0):
        if (counter % prime[-i] == 0):
            return "found"
return "not found"

然而,实现的问题是存储/乘以素数在传统模型中仅为O(1),但如果数组(即n)足够大,则此模型不适用

但是,它是一种理论算法,可以完成这项工作。

答案 7 :(得分:0)

这是IVlad解决方案的一个细微变化,我认为它在概念上更简单,并且n log n但是比较较少。一般的想法是从排序数组的两端开始,并将索引朝向彼此。在每一步,只移动数组值从0开始的索引 - 仅在Theta(n)比较中,你就会知道答案。

sort the array (n log n)

loop, starting with i=0, j=n-1
  if a[i] == -a[j], then stop:
    if a[i] != 0 or i != j, report success, else failure
  if i >= j, then stop: report failure
  if abs(a[i]) > abs(a[j]) then i++ else j--

(是的,可能是我在这里没有想到的一堆角落案例。你可以感谢那品脱的自制葡萄酒。)

如,

[ -4, -3, -1, 0, 1, 2 ]     notes:
  ^i                ^j      a[i]!=a[j], i<j, abs(a[i])>abs(a[j])
      ^i            ^j      a[i]!=a[j], i<j, abs(a[i])>abs(a[j])
          ^i        ^j      a[i]!=a[j], i<j, abs(a[i])<abs(a[j])
          ^i     ^j         a[i]==a[j] -> done

答案 8 :(得分:-1)

如果一个是另一个的负数,则两个整数的总和只能为零,如7和-7,或2和-2。