代码改变世界

双数和(Two Sum)

2015-01-23 18:51  coding-pig  阅读(293)  评论(0)    收藏  举报

给定一个整数数列,找出两个数,使其和等于给定值K,并返回其下标。如果无法找到两个数,返回(-1,-1)。

经典面试问题。主要考察设计算法是对时间和空间复杂度的把握。

首先一些基本思考:

  1. 问题的边界条件是什么?数据有何特点?数据量有多大?
  2. 最简单的暴力解法是什么?进一步优化的途径何在?

首先该数组可能存在重复元素,如(3,1,1,4,2)。如果K值设定为3,则可能返回的结果为(1,4)或者(2,4)。注意我们要返回的是下标。由于原题没有指明返回符合条件的多个可能解的限制条件,顾返回任何一对下标都是接受的。数据元素为整数。题目没有说明数组的长度,故先可假定数组可放入内存,而先无须顾虑用序列化,分布式或并行解法。

最简单的暴力解法是穷举所有不同元素对,当发现其和等于K时,返回该元素对的下标。如果没找到满足条件的元素对,则返回(-1,-1)。Python代码如下:

1 def find_two_sum(values, K):
2     if len(values) >= 2:
3         for i in range(len(values)-1):
4             for j in range(i+1, len(values)):
5                 if values[i] + values[j] == K:
6                      return (i,j)   
7     return (-1,-1)

显然,上述算法简明,不占额外空间,在数据量小时是不错的解决方案。当数据量大时,因为需要扫描数组两遍,故效率不高。该算法的空间复杂度为常数,时间复杂度为二次。

 如果要改善时间复杂度,则需要防止扫描数组两遍。这是想象当我们在扫描数组是遇到一个新元素A,我们需要找到一个以前访问过的元素B,使得这两个元素的和为给定值。由于我们不可能再次访问原数组去获得B,因此我们需要用一种机制迅速提取出B。由于B的值应该为K-A,故可以设立一个哈希集合存储所有访问过的元素。显然,查询B的时间是常数级别的。代码如下:

1 def find_two_sum(values, K):
2     if len(values) >= 2:
3         visited = {}
4         for i in range(len(values)):
5             if values[i] not in visited:
6                 if K - values[i] in visited:
7                     return (visited[K-values[i]], i)
8                 visited[values[i]] = i
9     return (-1, -1)
Hash map

 上述算法的代价是需要O(N)的空间存放哈希集合。另外,哈希集合的实现也会影响算法实际运行的效率。

最后的一个特殊例子。如果数组已经被排序,则无须额外空间,可以线性时间复杂度找到解。代码如下:

 1 def find_two_sum(sorted_values, K):
 2     if len(sorted_values) >= 2:
 3         start, end = 0, len(sorted_values)-1
 4         while start < end:
 5             if sorted_values[start] + sorted_values[end] == K:
 6                 return (start, end)
 7             elif sorted_values[start] + sorted_values[end] < K:
 8                 start += 1
 9             else:
10                 end -= 1
11     return (-1, -1)

在本题中,如果数组无序,则可产生一个数组初始化为(0,1,...,N-1),对应原来输入数组的下标。然后对此数组排序使得其开始元素对应原始数组最小元素的下标。然后,再应用上述算法。但此时时间复杂度为O(NlgN),空间复杂度为O(N),相对基于哈希集合的算法并无优势。