不是一个家庭作业问题,而是一个可能的面试问题......
寻找非暴力方法
答案 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来抵消它。
此代码中唯一的特殊情况是:
与最棘手的“最小化时间复杂度”算法一样,这个算法有一个权衡,因为它可能具有更高的空间复杂度(例如当阵列中只有一个元素恰好是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):
现在,这是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。