查找–对撞指针
给出一个整型数组nums,返回这个数组中两个数字的索引值i和j,使得nums[i] + nums[j]等于一个给定的target值,两个索引不能相等。
如:nums= [2,7,11,15],target=9
返回[0,1]
思路:
- 开始数组是否有序;
- 索引从0开始计算还是1开始计算?
- 没有解该怎么办?
- 有多个解怎么办?保证有唯一解。
先尝试强行解,O(n2)遍历
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
len_nums = len(nums)
for i in range(len_nums):
for j in range(i+1,len_nums):
if nums[i] + nums[j] == target:
return [i,j]
尝试优化:用排序+指针对撞(O(n) + O(nlogn) = O(nlogn))
因为问题本身不是有序的,因此需要对原来的数组进行一次排序,排序后就可以用O(n)的指针对撞进行解决。
因为返回的是索引值, 所以在排序前要先将数字所对应的索引值储存
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = dict()
nums_copy = nums.copy()
sameFlag = True;
nums.sort()
l,r = 0,len(nums)-1
while l < r:
if nums[l] + nums[r] == target:
break
elif nums[l] + nums[r] < target:
l += 1
else:
r -= 1
res = []
for i in range(len(nums)):
if nums_copy[i] == nums[l] and sameFlag:
res.append(i)
sameFlag = False
elif nums_copy[i] == nums[r]:
res.append(i)
return res
更加pythonic的实现:
通过list(enumerate(nums))开始实现下标和值的绑定,不用专门的再copy加bool判断。
class Solution:
nums = list(enumerate(nums))
nums.sort(key = lambda x:x[1])
i,j = 0, len(nums)-1
while i < j:
if nums[i][1] + nums[j][1] > target:
j -= 1
elif nums[i][1] + nums[j][1] < target:
i += 1
else:
if nums[j][0] < nums[i][0]:
nums[j],nums[i] = nums[i],nums[j]
return num[i][0],nums[j][0]
拷贝数组 + bool型变量辅助
查找表–O(n)
遍历数组过程中,当遍历到元素v时,可以只看v前面的元素,是否含有target-v的元素存在。
- 如果查找成功,就返回解;
- 如果没有查找成功,就把v放在查找表中,继续查找下一个解。
即使v放在了之前的查找表中覆盖了v,也不影响当前v元素的查找。因为只需要找到两个元素,只需要
找target-v的另一个元素即可。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = dict()
for i in range(len(nums)):
complement = target - nums[i]
# 已经在之前的字典中找到这个值
if record.get(complement) is not None:
res = [i,record[complement]]
return res
record[nums[i]] = i
题目描述
给出一个整型数组,寻找其中的所有不同的三元组(a,b,c),使得a+b+c=0
注意:答案中不可以包含重复的三元组。
如:nums = [-1, 0, 1, 2, -1, -4],
结果为:[[-1, 0, 1],[-1, -1, 2]]
解题思路
- 数组不是有序的;
- 返回结果为全部解,多个解的顺序是否需要考虑?–不需要考虑顺序
- 什么叫不同的三元组?索引不同即不同,还是值不同?–题目定义的是,值不同才为不同的三元组
- 没有解时怎么返回?–空列表
class Solution:
def threeSum(self, nums: [int]) -> [[int]]:
nums.sort()
res = []
for i in range(len(nums)-2):
# 因为是排序好的数组,如果最小的都大于0可以直接排除
if nums[i] > 0: break
# 排除i的重复值
if i > 0 and nums[i] == nums[i-1]: continue
l,r = i+1, len(nums)-1
while l < r:
sum = nums[i] + nums[l] + nums[r]
if sum == 0:
res.append([nums[i],nums[l],nums[r]])
l += 1
r -= 1
#防止重复
while l < r and nums[l] == nums[l-1]: l += 1
while l < r and nums[r] == nums[r+1]: r -= 1
elif sum < 0:
l += 1
else:
r -= 1
return res
小套路
- 采用for + while的形式来处理三索引;
- 当数组不是有序时需要注意,有序的特点在哪里,有序就可以用哪些方法解决?无序的话不便在哪
里? - 对撞指针套路:
# 对撞指针套路
l,r = 0, len(nums)-1
while l < r:
if nums[l] + nums[r] == target:
return nums[l],nums[r]
elif nums[l] + nums[r] < target:
l += 1
else:
r -= 1`
处理重复值的套路:先转换为有序数组,再循环判断其与上一次值是否重复:
# 1.
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i-1]: continue
# 2.
while l < r:
while l < r and nums[l] == nums[l-1]: l += 1
题目描述
给出一个整形数组,寻找其中的所有不同的四元组(a,b,c,d),使得a+b+c+d等于一个给定的数字target。
如:
nums = [1, 0, -1, 0, -2, 2],target = 0
结果为:
[[-1, 0, 0, 1],[-2, -1, 1, 2],[-2, 0, 0, 2]]
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
res = []
if len(nums) < 4: return res
if len(nums) == 4 and sum(nums) == target:
res.append(nums)
return res
for i in range(len(nums)-3):
if i > 0 and nums[i] == nums[i-1]: continue
for j in range(i+1,len(nums)-2):
if j > i+1 and nums[j] == nums[j-1]: continue
l,r = j+1, len(nums)-1
while l < r:
sum_value = nums[i] + nums[j] + nums[l] + nums[r]
if sum_value == target:
res.append([nums[i],nums[j],nums[l],nums[r]])
l += 1
r -= 1
while l < r and nums[l] == nums[l-1]: l += 1
while l < r and nums[r] == nums[r+1]: r -= 1
elif sum_value < target:
l += 1
else:
r -= 1
return res
还可以使用combinations(nums, 4)来对原数组中得4个元素全排列,在开始sort后,对排列得到得元素进行set去重。但单纯利用combinations实现会超时。
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
from itertools import combinations
res = []
for i in combinations(nums, 4):
if sum(i) == target:
res.append(i)
res = set(res)
return res
题目描述
给出一个整形数组,寻找其中的三个元素a,b,c,使得a+b+c的值最接近另外一个给定的数字target。
如:给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
分析
判断三个数之和是否等于target值, 如果等于则直接返回这三个值, 如果不等于target值则比较数组中三数组合的和与target之间的差值, 返回差值最小的三个数。
nums.sort()
res = []
for i in range(len(nums)-2):
l,r = i+1, len(nums)-1
while l < r:
sum = nums[i] + nums[l] + nums[r]
if sum == 0:
res.append([nums[i],nums[l],nums[r]])
elif sum < 0:
l += 1
else:
r -= 1
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
diff = abs(nums[0]+nums[1]+nums[2]-target)
res = nums[0] + nums[1] + nums[2]
for i in range(len(nums)):
l,r = i+1,len(nums)-1
t = target - nums[i]
while l < r:
if nums[l] + nums[r] == t:
return nums[i] + t
else:
if abs(nums[l]+nums[r]-t) < diff:
diff = abs(nums[l]+nums[r]-t)
res = nums[i]+nums[l]+nums[r]
if nums[l]+nums[r] < t:
l += 1
else:
r -= 1
return res
题目描述
给出四个整形数组A,B,C,D,寻找有多少i,j,k,l的组合,使得A[i]+B[j]+C[k]+D[l]=0。其
中,A,B,C,D中均含有相同的元素个数N,且0<=N<=500;
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:2
分析实现
这个问题同样是Sum类问题得变种,其将同一个数组的条件,变为了四个数组中,依然可以用查找表的思想来实现。
首先可以考虑把D数组中的元素都放入查找表,然后遍历前三个数组,判断target减去每个元素后的值是否在查找表中存在,存在的话,把结果值加1。那么查找表的数据结构用dict来存储重复的元素,最后的结果值加上dict相应key的value,代码如下:
from collections import Counter
record = Counter()
# 先建立数组D的查找表
`for i in range(len(D)):
record[D[i]] += 1
res = 0
for i in range(len(A)):
for j in range(len(B)):
for k in range(len(C)):
num_find = 0-A[i]-B[j]-C[k]
if record.get(num_find) != None:
res += record(num_find)
return res
此时因为遍历三层 时间复杂度为O(n3), N<= 500, 如果N过大那该算法消耗依旧过大,优化:
class Solution:
def fourSumCount(self, A: List[int], B: List[int], C: List[int], D:
List[int]) -> int:
from collections import Counter
record = Counter()
for i in range(len(A)):
for j in range(len(B)):
record[A[i]+B[j]] += 1
res = 0
for i in range(len(C)):
for j in range(len(D)):
find_num = 0 - C[i] - D[j]
if record.get(find_num) != None:
res += record[find_num]
return res
该算法复杂度为O(n2)
再使用Pythonic的列表生成式和sum函数进行优化,如下:
class Solution:
def fourSumCount(self, A: List[int], B: List[int], C: List[int], D:
List[int]) -> int:
record = collections.Counter(a + b for a in A for b in B)
return sum(record.get(- c - d, 0) for c in C for d in D)
题目描述
给出一个字符串数组,将其中所有可以通过颠倒字符顺序产生相同结果的单词进行分组。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”],
输出:[[“ate”,“eat”,“tea”],[“nat”,“tan”],[“bat”]]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
from collections import defaultdict
strs_dict = defaultdict(list)
res = []
for str in strs:
key = ''.join(sorted(list(str)))
strs_dict[key] += str.split(',')
for v in strs_dict.values():
res.append(v)
return res
再将能用列表生成式替换的地方替换掉,代码实现如下:
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
from collections import defaultdict
strs_dict = defaultdict(list)
for str in strs:
key = ''.join(sorted(list(str)))
strs_dict[key] += str.split(',')
return [v for v in strs_dict.values()]
题目描述
给出一个平面上的n个点,寻找存在多少个由这些点构成的三元组(i,j,k),使得i,j两点的距离等于i,k两点的距离。
其中n最多为500,且所有的点坐标的范围在[-10000,10000]之间。
输入:
[[0,0],[1,0],[2,0]]
输出:
2
解释:
两个结果为: [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]
距离
对于距离值的求算,按照欧式距离的方法进行求算的话,容易产生浮点数,可以将根号去掉,用差的平方和来进行比较距离。
实现代码如下:
class Solution:
def numberOfBoomerangs(self, points: List[List[int]]) -> int:
res = 0
from collections import Counter
for i in points:
record = Counter()
for j in points:
if i != j:
record[self.dis(i,j)] += 1
for k,v in record.items():
res += v*(v-1)
return res
def dis(self,point1,point2):
return (point1[0]-point2[0]) ** 2 + (point1[1]-point2[1]) ** 2
优化
对实现的代码进行优化:
- 将for循环遍历改为列表生成式;
- 对sum+=的操作,考虑使用sum函数。
- 对不同的函数使用闭包的方式内嵌;
class Solution:
def numberOfBoomerangs(self, points: List[List[int]]) -> int:
from collections import Counter
def f(x1, y1):
# 对一个i下j,k的距离值求和
d = Counter((x2 - x1) ** 2 + (y2 - y1) ** 2 for x2, y2 in
points)
return sum(t * (t-1) for t in d.values())
# 对每个i的距离进行求和
return sum(f(x1, y1) for x1, y1 in points)
题目描述
给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。
示例 1:
输入: [[1,1],[2,2],[3,3]]
输出: 3
示例 2:
输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出: 4
分析实现
本道题目的要求是:看有多少个点在同一条直线上,那么判断点是否在一条直线上,其实就等价于判断i,j两点的斜率是否等于i,k两点的斜率。
回顾上道447题目中的要求:使得i,j两点的距离等于i,k两点的距离,那么在这里,直接考虑使用查找表实现,即查找相同斜率key的个数value是多少。
在上个问题中,i和j,j和i算是两种不同的情况,但是这道题目中,这是属于相同的两个点,因此在对遍历每个i,查找与i相同斜率的点时,不能再对结果数res++,而应该取查找表中的最大值。如果有两个斜率相同时,返回的应该是3个点,故返回的是结果数+1。
class Solution:
def maxPoints(self,points):
if len(points) <= 1:
return len(points)
res = 0
from collections import defaultdict
for i in range(len(points)):
record = defaultdict(int)
samepoint = 0
for j in range(len(points)):
if points[i][0] == points[j][0] and points[i][1] == points[j][1]:
samepoint += 1
else:
record[self.get_Slope(points,i,j)] += 1
for v in record.values():
res = max(res, v+samepoint)
res = max(res, samepoint)
return res
def get_Slope(self,points,i,j):
if points[i][1] - points[j][1] == 0:
return float('Infinite')
return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])
该解决方法时间复杂度为O(n2), 空间复杂度为O(n).