1、问题类型特征
从数组中找出K个数,使得这K个数的和与target满足一定的关系。
2、通用解题思路
- 暴力解法:使用K层循环,最常见,但是效率极低;
- hash-map:建立一个hash-map循环遍历一次即可,这个方法适用于K=2的情况。
- two-pointers:先对数组进行排序,然后使用K-2重循环固定K-2个数,最后使用双指针寻找剩下的2个数,这种方法的时间复杂度为 O ( n K − 1 ) O(n^{K-1}) O(nK−1)
3、问题实例
Leetcode中属于该类型的题目:
序号 | 题目 | 问题难度 |
---|---|---|
1 | Two Sum | easy |
167 | Two Sum II-Input array is sorted | easy |
15 | 3Sum | medium |
16 | 3Sum Closet | medium |
259 | 3Sum Smaller | medium |
18 | 4Sum | medium |
(1)Two Sum II-Input array is sorted
- 问题描述
输入一个排好序的数组和数target,从数组中找出两个数,使得这两个数的和等于target。 - 参考:《剑指offer》-[第6章:面试中的各种能力-6.3:知识迁移的能力]-题41:和为s的两个数vs和为s的连续正数序列
(2)Two Sum
- 问题描述
输入一个数组和数target,从数组中找出两个数,使得这两个数的和等于target。 - 解题思路
- 思路1:有了问题(1)的基础,我们就可以先对数组进行排序,然后再找出和为target的两个数。如果题目要求只是找到这两个数,直接返回即可;但如果要求返回这两个数在原数组中的索引,由于对原数组进行排序会打乱原数组中元素的顺序,所以需要使用一个额外的数据结构(哈希表,结构体,或类)记录元素在原数组中的索引。这种方法花费的时间主要在排序上,所以时间复杂度为 O ( n ) O(n) O(n),如果最终返回的是索引,那么空间复杂度为 O ( n ) O(n) O(n)。
- 思路2:还有一种时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n)的方法。这种方法的基本思路是采用哈希表来记录每个数的相反数的索引,从头到尾遍历数组,每遍历一个数,看哈希表的键集合中是否包含该数,如果包含,则可以直接得到它相反数的索引,如果不包含,则将(它的相反数,它自身的索引)作为键值对插入哈希表。
- 代码实现
def TwoSum(array, target):
"""
:type array:list[int]
:type targt:int
:rtype list[tuple]
"""
if len(array) == 0 or array == None:
return None
oppsite = {}
twosum = []
for (i,element) in enumerate(array):
if element in oppsite.keys():
twosum.append((oppsite[element],i))
else:
oppsite[target - element] = i
return twosum
(3)3Sum
- 问题描述
输入一个数组和一个数target,从数组中找出三个数,使得这三个数的和为target。找出所有满足条件且不重复的三元组。 - 解题思路
- 思路1:首先对数组进行排序,然后用一个循环固定一个数,之后再使用双指针,从该数后面的所有数查找两个数,使得这两个数之和等于target。由于不能包含重复的三元组,所以在外层循环遍历和内层双指针查找的时候,都要去除重复的元素。这种方法的时间复杂度为 O ( n 2 ) O(n^{2}) O(n2)。
- 代码实现
def ThreeSum(array, target):
"""
:type array:list[int]
:type targt:int
:rtype list[tuple]
"""
if len(array) < 3 or array == None :
return None
result = []
array = sorted(array)
for i in range(0,len(array)):
if i > 0 and array[i] == array[i-1]:
continue
start = i + 1
end = len(array) - 1
while start < end:
sum = array[start] + array[end] + array[i]
if sum == target:
result.append((array[i],array[start],array[end]))
start += 1
end -= 1
while start < end and array[start] == array[start -1]:
start += 1
while start < end and array[end] == array[end + 1]:
end -= 1
elif sum < target:
start += 1
else:
end -= 1
return result
(4)3Sum Closet
- 问题描述
输入一个数组和数target,从数组中找出三个数,使这三个之和最接近target。 - 解题思路
- 思路1:这道题是3sum问题的简单变形。3sum问题是要求三个数的和与target的差值为0,而3Sum Closet问题则是要求三个数的和与target的差值的绝对值尽可能接近0。所以,只需要稍加修改3Sum问题的代码,即可实现3Sum Closet问题的求解。同样是先对数组进行排序,接着使用一个循环固定一个数,然后使用双指针在固定的数后面寻找另外两个数,在两个指针相遇前,每次都计算外层循环和双指针指向的三个数之和,并使用变量closet记录三数之和与target的差值的绝对值,若本次得到的closet比上次小,则对closet进行更新,否则不更新。之后,比较三数之和与target的大小,如果小于target,则前面的指针向后移动,如果大于target,则后面的指针向前移动。
- 代码实现
def ThreeSumCloset(array, target):
"""
:type array:list[int]
:type targt:int
:rtype list[tuple]
"""
if len(array) < 3 or array == None :
return None
result = None
array = sorted(array)
closet = abs(sum(array[:3]) - target)
for i in range(0,len(array)):
if i > 0 and array[i] == array[i-1]:
continue
start = i + 1
end = len(array) - 1
while start < end:
sum_ = array[start] + array[end] + array[i]
if sum_ == target:
result = (array[i],array[start],array[end])
return result
diff = abs(sum_ - target)
if diff < closet:
closet = diff
result = (array[i],array[start],array[end])
while start < end and array[start] == array[start - 1]:
start += 1
while start < end and end < len(array) - 1 and array[end] == array[end + 1]:
end -= 1
if sum_ < target:
start += 1
elif sum_ > target:
end -= 1
return result
(5)3Sum Smaller
- 问题描述
输入一个整数数组array和整数target,从数组array中找到这样的索引三元组(i,j,k),使得array[i] 、array[j] 、array[k]之和小于target,同时满足i < j < k。求这样的三元组的数量。 - 解题思路
- 思路1:和问题(3)、(4)是一样的思路。
- 代码实现
def ThreeSumCloset(array, target):
"""
:type array:list[int]
:type targt:int
:rtype list[tuple]
"""
if len(array) < 3 or array == None :
return None
result = []
array = sorted(array)
for i in range(0,len(array)):
if i > 0 and array[i] == array[i-1]:
continue
start = i + 1
end = len(array) - 1
while start < end:
sum_ = array[start] + array[end] + array[i]
while start < end and array[start] == array[start - 1]:
start += 1
while start < end and end < len(array) - 1 and array[end] == array[end + 1]:
end -= 1
if sum_ < target:
result.append((i, start,end))
start += 1
elif sum_ > target:
end -= 1
return len(result)
(6)4Sum
- 问题描述
给出一个整数数组和目标数target,从中找到所有可能的4个数,使得这四个数的和等于目标数。注意不能有重复的情况。 - 解题思路:
*思路1: 这道题可以才有用与3sum同样的思路。只不过需要外部需要使用两层循环来固定两个数。时间复杂度为 O ( n 3 ) O(n^{3}) O(n3)。