检查给定整数是否等于int数组的两个元素之和的最佳算法是什么?

时间:2018-09-30 04:47:43

标签: python algorithm

def check_set(S, k):    
S2 = k - S
set_from_S2=set(S2.flatten())
for x in S:    
    if(x in set_from_S2):    
        return True
return False

我有一个给定的整数k。我想检查k是否等于数组S的两个元素的和。

S = np.array([1,2,3,4])
k = 8

在这种情况下,它应该返回False,因为S中没有两个元素的总和为8。上面的代码像8 = 4 + 4一样工作,因此它返回了True

我找不到复杂度为O(n)的算法来解决这个问题。

有人可以帮助我吗?

2 个答案:

答案 0 :(得分:6)

您必须考虑同一项目的多个实例,因此在此处设置不是一个好的选择。

相反,您可以使用value_field = number_of_keys(作为变体-from collections import Counter)来利用字典

A = [3,1,2,3,4]
Cntr = {}
for x in A:
    if x in Cntr:
        Cntr[x] += 1
    else:
        Cntr[x] = 1

#k = 11
k = 8
ans = False
for x in A:
    if (k-x) in Cntr:
        if k == 2 * x:
            if Cntr[k-x] > 1:
                ans = True
                break
        else:
            ans = True
            break
print(ans)

对于k = 5,6(我又添加3)返回True,对于k = 8,11返回False

答案 1 :(得分:4)

添加到MBo的答案中。

就算法学而言,“最优”可能是一个模棱两可的术语,因为在算法运行速度与内存效率之间经常存在折衷。有时我们也可能对最坏情况下的资源消耗或平均资源消耗感兴趣。我们将在最坏的情况下循环,因为它更简单,并且在我们的方案中大致等于平均值​​。

我们调用n数组的长度,让我们考虑3个示例。

示例1

我们从一个非常幼稚的算法开始,针对这个问题,有两个嵌套循环遍历数组,并检查每两个具有不同索引的项的总和是否等于目标数。

  • 时间复杂度:最坏的情况(答案为False或答案为True,但我们在检查的最后一对项目中找到了答案)具有{{1} }循环迭代。如果您熟悉big-O表示法,我们会说该算法的时间复杂度为n^2,这基本上意味着,根据我们的输入大小O(n^2)而言,求解该问题所需的时间算法的增长或多或少像n一样具有乘数因子(嗯,从技术上讲,该符号的意思是“ 最多像 n^2一样具有乘数因子,但这是对语言的普遍滥用改为使用“或多或少”。

  • 空间复杂度(内存消耗):我们只存储一个数组,以及一组固定的对象,这些对象的大小不依赖于n^2(Python需要运行的所有东西,调用堆栈,也许是两个迭代器)和/或一些临时变量)。因此,n所增加的一部分内存消耗仅是数组的大小,它是n乘以在数组中存储整数所需的内存量(我们称{{1 }}。

结论:时间是n,内存是sizeof(int)O(n^2),也就是说,取决于一个附加的常数,这对我们来说无关紧要,而我们会从现在开始忽略。)

示例2

让我们在MBo的答案中考虑算法。

  • 时间复杂度:比示例1好得多。我们首先创建一个字典。这是在n*sizeof(int)上循环进行的。在适当的条件下,在字典中设置键是恒定时间的操作,因此该第一循环的每个步骤所花费的时间不取决于+O(1)。因此,就时间复杂度而言,目前我们使用n。现在,我们在n上只剩下一个循环。访问字典中元素所花费的时间与O(n)无关,因此,总复杂度再次为n。将两个循环组合在一起,因为它们都像n一样增长到一个乘法因子,所以它们的总和(直到一个不同的乘法因子)也一样。总计:O(n)

  • 内存:与以前基本相同,外加n个元素的字典。为了简单起见,让我们考虑这些元素是整数(我们本可以使用布尔值),而忽略了字典的某些方面,只计算用于存储键和值的大小。有O(n)个整数键和n个整数值要存储,它们在内存上使用n。加上以前的内容,我们总共有n

结论:时间为2*n*sizeof(int),内存为3*n*sizeof(int)。当O(n)增长时,该算法的速度要快得多,但使用的内存却比示例1多三倍。在一些几乎没有可用内存的怪异场景中(可能是嵌入式系统),这个3*n*sizeof(int)可能太多了,而且您可能无法使用此算法(不可否认,它可能永远不会成为一个真正的问题)。

示例3

我们可以在示例1和示例2之间找到权衡吗?

一种实现方法是复制与示例1中相同的嵌套循环结构,但需要进行一些预处理以更快地替换内部循环。为此,我们对初始数组进行排序。用well-chosen algorithms完成,它的时间复杂度为n,并且内存使用率可以忽略不计。

对数组进行排序后,我们将编写外循环(这是整个数组的常规循环),然后在该外循环内,使用二分法搜索缺少的数字以达到目标{ {1}}。这种二分法的内存消耗为3*n*sizeof(int),时间复杂度也为O(n*log(n))

  • 时间复杂度:预处理排序为k。然后,在算法的主要部分中,我们对O(log(n))二分搜索进行了O(log(n))调用,总计为O(n*log(n))。因此,总体而言,n

  • 内存:忽略常数部分,我们拥有数组的内存(O(log(n)))和二分法搜索(O(n*log(n)))中的调用堆栈内存。总计:O(n*log(n))

结论:时间为n*sizeof(int),内存为O(log(n))。内存几乎与示例1中的内存一样小。时间复杂度比示例2中的稍大。在由于缺少内存而无法使用示例2的情况下,就速度而言,次优的选择实际上是示例3,即几乎与示例2一样快,并且如果示例1的速度很慢,则可能有足够的运行空间。

总体结论

这个答案只是为了表明“最优”在算法上是上下文相关的。在这个特定示例中,人们不太可能选择实现示例3。通常,如果n*sizeof(int) + O(log(n))太小,您会看到示例1,那么人们会选择最简单的设计和最快的编码,或示例2,如果O(n*log(n))大一点,我们想要速度。但是,如果您查看我链接用于排序算法的Wikipedia页面,您会发现它们都不是所有方面的最佳选择。他们都有可以用更好的东西代替的方案。