两数之和(two sum)是很典型的笔试面试题目,也是leetcode的第一题。为了便于叙述,我把原问题简化一下:
给定数组a[N], 判断是否存在两个元素的和为k.
算法1: 两层循环
针对每一个a[i], 我们再次遍历数组a[N], 然后判断a[i]+a[j]的值是否等于k, 其中 j > i. 这是最直观最暴力的解法。时间复杂度是O(N*N), 空间复杂度是O(1).
如果这样解答,虽然正确,但肯定是无法通过笔试面试的。
分析一下,针对每一个a[i], 在找合理a[j]的过程中,用的是线性查找,显然就中招了,为什么不用哈希查找呢?
算法2: 单层循环+哈希表
我们来对算法1进行改进:
针对每一个a[i],需要在数组a[N]中查找k-a[i]的值,而最快的查找是哈希查找(时间复杂度是O(1)). 所以,整体的时间复杂度是O(N). 由于用到了哈希表,所以空间复杂度也是O(N).
这种以空间换时间的做法,是经常用到的。这种解法是满足笔试面试要求的。
代码实现如下(结果是满足条件的值的下标):
func twoSum(nums []int, target int) []int {
var result []int
m := make(map[int]int)
lenN := len(nums)
for i, v := range nums {
m[v] = i
}
for i := 0; i < lenN; i++ {
if j, ok := m[target - nums[i]]; ok && i < j {
result = append(result, i, j)
}
}
return result
}
测试结果如下:
如上代码,虽然是单层循环,但循环用了2次,实际上还可以继续优化,统一到一个循环中。这个优化不难,故不再赘述。
算法3: 快排+双指针
如果要求空间复杂度为O(logN),时间复杂度尽可能低,那该怎么办呢?
可以考虑快速排序和双指针法,具体如下:
步骤一:快速排序
步骤二:双指针夹逼,如下:
循环条件是while(left < high):
if a[left]+a[right]刚好等于k, 那么就找到了和为k的两个数。
if a[left]+a[right]比k小, 则left++, 用更大的数来尝试。
if a[left]+a[right]比k大, 则righ--, 用更小的数来尝试。
这种解法也挺巧妙的,整体的时间复杂度为O(N*logN), 空间复杂度为O(logN), 也是可以满足笔试面试要求的。
上面讨论了两数之和,那么三数之和的问题怎么处理呢?显然,也是3种方法:
算法1:三层循环(这种暴力解法无法通过笔试面试)
时间复杂度为O(N*N*N), 空间复杂度为O(1).
算法2:双循环+哈希表
时间复杂度为O(N*N), 空间复杂度为O(N).
算法3:快排+双指针
时间复杂度为O(N*N), 空间复杂度为O(logN).
阿里、腾讯、头条的面试官真的好喜欢leetcode. 祝拿到心仪的offer.