两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
解题思路:
第一反应是暴力破解,但是超时了(理所当然的失败)。
讨论区提示用哈希表,python中可以使用字典,像这种需要索引和值的,可以用到enumerate函数。
首先创建空字典dic用于保存索引和值
使用enumerate遍历nums中的索引和值,这里的值称为num1
计算num2 = target - num1,检查num2是否存在于字典dic中
如果存在,那么自然返回num2的索引dic[num2]和num1的索引index
如果不存在,那么将当前的num1值与其索引存入字典dic中
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
hashset={}
for i in range(len(nums)):
if hashset.get(target-nums[i]) is not None :
return [hashset.get(target-nums[i]),i]
hashset[nums[i]]=i
三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
解题思路:
固定一个值a,然后在剩余的值中找b,c使得a+b+c=0。
因此至少需要两种循环,一个遍历找a,另一个遍历找b,c,那么就可以用上排序,毕竟两个循环都要O(N^2)了。
对数组排序后,从前往后遍历固定一个值a,我们需要找到b+c = -a的b,c,那么怎么找剩余两个值呢?
可以使用二分的思想,对于一个排好序的数组,将其头尾相加,与目标值对比会有三种结果
1.相等,说明找到了
2.大于目标值,说明尾部太大,将尾部索引左移
3.小于目标值,说明头部太小,将头部索引右移
此外还有一些细节,比如需要跳过和上一次枚举相同的数,以及保持左指针小于右指针
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
#固定一个值 然后对剩余值进行搜索
for i in range(len(nums)-2):
#跳过重复的结果
if i>0 and nums[i] == nums[i-1]:
continue
left = i + 1
right = len(nums) - 1
target = -nums[i]
while left<right:
if nums[left] + nums[right] == target:
res.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1
#跳过重复的结果
while left<right and nums[left] == nums[left-1]:
left += 1
#跳过重复的结果
while left<right and nums[right] == nums[right+1]:
right -= 1
elif nums[left] + nums[right] > target:
right -= 1
else:
left += 1
return res
最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
提示:
3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4
解题思路:
和上一题一样,排序后双指针遍历,题目要求返回与target最接近(差值的绝对值最小)的三数和(一开始还以为返回那三个数,晕),所以我们用三个数的和sum_3与target进行对比,还是一样,固定一个数,然后使用两个指针,分别从头部和尾部进行遍历,还是有三种结果:
1.三数和=target,直接返回三数和
2.三数和大于target:尾部值大了,右指针左移
3.三数和小于target:头部值小了,左指针右移
然后我们需要更新三数和与target间的差值,最终返回一个差值最小的三数和
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
best = 10**7
# 根据差值的绝对值来更新答案
def update(cur):
nonlocal best
if abs(cur - target) < abs(best - target):
best = cur
#固定一个值,然后搜索剩余值
for i in range(len(nums) - 2):
#跳过和上次枚举相同的值
if i>0 and nums[i] == nums[i-1]:
continue
left = i + 1
right = len(nums) - 1
while left < right:
sum_3 = nums[i] + nums[left] + nums[right]
#如果三数之和恰好等于目标,直接返回
if target == sum_3:
return target
#更新差值
update(sum_3)
#如果三数之和小于目标,则左指针右移
if target > sum_3:
left += 1
#跳过和上次枚举相同的值
while left<right and nums[left] == nums[left-1]:
left += 1
#否则右指针左移
else:
right -= 1
#跳过和上次枚举相同的值
while left<right and nums[right] == nums[right+1]:
right -= 1
return best
小优化(减少枚举次数,但是无法减少时间复杂度):
当我们枚举 a,b,c 中任意元素并移动指针时,可以直接将其移动到下一个与这次枚举到的不相同的元素,减少枚举的次数。
if s > target:
# 如果和大于 target,移动 c 对应的指针
k0 = k - 1
# 移动到下一个不相等的元素
while j < k0 and nums[k0] == nums[k]:
k0 -= 1
k = k0
else:
# 如果和小于 target,移动 b 对应的指针
j0 = j + 1
# 移动到下一个不相等的元素
while j0 < k and nums[j0] == nums[j]:
j0 += 1
j = j0
时间复杂度:O(N^2),其中 N 是数组nums 的长度。我们首先需要 O(N \log N)的时间对数组进行排序,随后在枚举的过程中,使用一重循环 O(N)枚举 a,双指针 O(N)枚举 b 和 c,故一共是 O(N^2)。
空间复杂度:O(logN)。排序需要使用O(logN) 的空间。然而我们修改了输入的数组 nums,在实际情况下不一定允许,因此也可以看成使用了一个额外的数组存储了nums 的副本并进行排序,此时空间复杂度为 O(N)。
四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 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]
]
解题思路:
老套娃了,把四数和看做两个两数和的和,令n等于数组长度
首先第一个数从0到n-4,第二个数从1到n-3,这两个数作为第一部分,在集合中保存所有可能出现的两数和以及两个数的索引
然后计算第二部分,第三个数从2到n-2,第四个数从3到n-1,计算所有可能的两数和,然后是目标值减去第二部分的两数和,判断结果是否存在第一部分的两数和,如果存在,还需要判断第一部分的第二个数索引比第三个数的索引小
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
d={}
n = len(nums)
res = set()
for i in range(n-3):
for j in range(i+1, n-2):
crr_sum = nums[i] + nums[j]
d.setdefault(crr_sum,[]).append((i,j))
for i in range(2, n-1):
for j in range(i+1, n):
crr_sum2 = nums[i] + nums[j]
pre_pairs = d.get(target - crr_sum2)
if pre_pairs == None:
continue
for pair in pre_pairs:
if pair[1] < i:
res.add((nums[pair[0]], nums[pair[1]], nums[i], nums[j]))
return list(res)
思路比较简单,使用了集合set达到去重的目的,最后需要转为列表返回
保存第一部分使用了字典的setdefault()方法:dict.setdefault(key,[]).append(value)
接收两个参数,如果key不在字典时,给字典的key设定一个值,值通过append方法添加
字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。
解题思路:
方法一:排序数组分类
字母异位词的排序字符串相等,比如‘eat’、‘tea’排序后都是‘aet’
排序后将键存储为散列化元组,如(‘a’,‘e’,‘t’),映射的value为输入的字符串列表
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
ans = collections.defaultdict(list)
for s in strs:
ans[tuple(sorted(s))].append(s)
print(ans)
return list(ans.values())
时间复杂度:O(NK log K),其中 N是 strs 的长度,而 K 是 strs 中字符串的最大长度。当我们遍历每个字符串时,外部循环具有的复杂度为 O(N)。然后,我们在 O(K log K) 的时间内对每个字符串排序。
空间复杂度:O(NK),排序存储在 ans 中的全部信息内容。
避免当Key不存在时,会引发‘KeyError’异常,可以使用collections类中的defaultdict()方法来为字典提供默认值。
语法格式:
collections.defaultdict([default_factory[, …]])
第一个参数为default_factory属性提供初始值,默认为None
1.使用list作第一个参数,可以将键-值对序列转换为列表字典。
from collections import defaultdict
s=[('yellow',1),('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d=defaultdict(list)
for k, v in s:
d[k].append(v)
a=sorted(d.items())
print(a)
当字典中没有的键第一次出现时,default_factory自动为其返回一个空列表,list.append()会将值添加进新列表;再次遇到相同的键时,list.append()将其它值再添加进该列表。
上一节的dict.setdefault()也可以实现相同的功能:
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d={}
for k, v in s:
d.setdefault(k,[]).append(v)
print('\n',d)
a=sorted(d.items())
print('\n',a)
2.将default_factory设为int还可以被用来计数:
from collections import defaultdict
s = 'mississippi'
d = defaultdict(int)
for k in s:
d[k] += 1
print('\n',d)
a=sorted(d.items())
print('\n',a)
字符串中的字母第一次出现时,字典中没有该字母,default_factory函数调用int()为其提供一个默认值0,加法操作将计算出每个字母出现的次数。
3.default_factory设为set时,可以用defaultdict建立集合字典
from collections import defaultdict
s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
d = defaultdict(set)
for k, v in s:
d[k].add(v)
print('\n',d)
a=sorted(d.items())
print('\n',a)
方法二:按计数分类
字母异位词每个字符的出现次数相同,将strs中每个字符串s转换为字符数count,count为26个非负整数数组,记录小写字符’a’-'z’的出现次数,最后将count为键,将字符作为value,存入相同的键中
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
ans = collections.defaultdict(list)
for s in strs:
count = [0] * 26
for c in s:
count[ord(c) - 97] += 1
ans[tuple(count)].append(s)
return list(ans.values())
时间复杂度:O(NK),其中 N 是 strs 的长度,而 K 是 strs 中字符串的最大长度。计算每个字符串的字符串大小是线性的,我们统计每个字符串。
空间复杂度:O(NK),排序存储在 ans 中的全部信息内容
存在重复元素 II
给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。
示例 1:
输入: nums = [1,2,3,1], k = 3
输出: true
示例 2:
输入: nums = [1,0,1,1], k = 1
输出: true
示例 3:
输入: nums = [1,2,3,1,2,3], k = 2
输出: false
解题思路:在一个数组里找另一个数,可以使用题目《两数和》的方法
使用字典保存索引和值,一次遍历数组,如果字典中有相同的值存在,判断字典中值的索引与当前值的索引差的绝对值是否小于等于k,如果是,则返回True;遍历结束后也没有找到,则返回False
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
hashset = {}
for i in range(len(nums)):
if nums[i] in hashset and abs(hashset[nums[i]] - i) <= k :
return True
hashset[nums[i]] = i
return False
存在重复元素 III
在整数数组 nums 中,是否存在两个下标 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值小于等于 t ,且满足 i 和 j 的差的绝对值也小于等于 ķ 。
如果存在则返回 true,不存在返回 false。
示例 1:
输入: nums = [1,2,3,1], k = 3, t = 0
输出: true
示例 2:
输入: nums = [1,0,1,1], k = 1, t = 2
输出: true
示例 3:
输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false
解题思路:桶排序,同一个桶之间的差的绝对值≤t
定义桶的大小是t+1, nums[i]//(t+1)决定放入几号桶,这样在一个桶里面的任意两个的绝对值差值都<=t
例如t=3, nums=[0 ,5, 1, 9, 3,4],那么0号桶就有[0,1,3],1号桶就有[4,5],2号桶就有[9]
遍历nums每一个元素,并把他们放入相应的桶中,有两种情况会返回True:
要放入的桶中已经有其他元素了,这时将nums[i]放进去满足差值<=t
可能存在前面一个桶的元素并且与nums[i]的差值<=t 或者 存在后面一个桶的元素并且与nums[i]的差值<=t
根据返回True的第一个条件,可以知道前后桶的元素最多也只能有一个。
接着考虑限制桶中的索引差最大为K,当i>=k的时候:
我们就要去删除存放着nums[i-k]的那个桶(编号为nums[i-k]//(t+1))
这样就能保证遍历到第i+1个元素时,全部桶中元素的索引最小值是i-k+1,就满足题目对索引的限制了
class Solution:
def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
if t<0 or k<0:
return False
#用字典模拟桶,key为桶号
all_buckets = {}
bucket_size = t + 1
for i in range(len(nums)):
bucket_num = nums[i] // bucket_size #决定放入几号桶
#桶中已经有元素,说明肯定存在两个数差的绝对值小于t
if bucket_num in all_buckets:
return True
#将元素放入桶中
all_buckets[bucket_num] = nums[i]
#前后桶的元素也可能与当前值相差的绝对值<=t
#检查前一个桶
if (bucket_num - 1) in all_buckets and abs(all_buckets[bucket_num - 1] - nums[i]) <= t:
return True
#检查后一个桶
if (bucket_num + 1) in all_buckets and abs(all_buckets[bucket_num + 1] - nums[i]) <= t:
return True
# 如果不构成返回条件,那么当i >= k 的时候就要删除旧桶了,以维持桶中的元素索引跟下一个i+1索引只差不超过k
if i >= k:
all_buckets.pop(nums[i-k]//bucket_size)
return False
回旋镖的数量
给定平面上 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]]
解题思路:要求输出个数,使用字典保存key为距离,value为出现该距离的次数
外层遍历每个点 i,内层遍历并计算其他点 j 到 i 的距离并通过 Map 保存相等距离的频次
计算距离公式不用开根号
计算排列组合公式 n * (n - 1)
class Solution:
def dis(self, p1, p2):
return (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1])
def numberOfBoomerangs(self, points: List[List[int]]) -> int:
res = 0
for i in points:
freqMap = {}
for j in points:
if j != i:
d = self.dis(i, j)
freqMap[d] = freqMap[d] + 1 if d in freqMap else 1
for v in freqMap.values():
res += v * (v - 1)
return res
四数相加 II
给定四个包含整数的数组列表 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 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
例如:
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:
- (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
- (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
解题思路:与四数和相同,先计算前面的两数和,将结果存入字典中,key为两数和,value为出现该两数和的次数;然后我们的target就是0-后面的两数和,在字典中找到target,并将计数器加上target出现的次数
还是使用defaultdict()方法,出现字典中没有的key时不会报错,factory_function设为int才可用于计数
class Solution:
def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
dic = defaultdict(int)
count = 0
for a in A:
for b in B:
dic[a+b] += 1
for c in C:
for d in D:
target = -(c + d)
if target in dic:
count += dic[target]
return count
直线上最多的点数
给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。
示例 1:
输入: [[1,1],[2,2],[3,3]]
输出: 3
解释:
^
|
| o
| o
| o
±------------>
0 1 2 3 4
示例 2:
输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出: 4
解释:
^
|
| o
| o o
| o
| o o
±------------------>
0 1 2 3 4 5 6
解题思路:没有 抄就完事了
固定一点, 找其他点和这个点组成直线, 统计他们的斜率
求斜率有两种方法:
用最大约数方法(gcd), 把他化成最简形式, 3/6 == 2/4 == 1/2
除数(不太精确, 速度快!)
由于需要考虑符号,所以要重写gcd函数;如果用除数的话,如果x,y相差特别大,可能会报错
class Solution:
def maxPoints(self, points: List[List[int]]) -> int:
from collections import Counter, defaultdict
# 统计所有点出现的次数
points_dict = Counter(tuple(point) for point in points)
# 保存所有点,结果不重复
not_repeat_points = list(points_dict.keys())
n = len(not_repeat_points)
if n == 1: return points_dict[not_repeat_points[0]]
res = 0
# 求最大公约数,用于求斜率,斜率需要保留符号
def gcd(x, y):
if y == 0:
return x
else:
return gcd(y, x % y)
for i in range(n - 1):
# 点1
x1, y1 = not_repeat_points[i][0], not_repeat_points[i][1]
# 斜率
slope = defaultdict(int)#用来计数,没有出现的key默认为0
for j in range(i + 1, n):
# 点2
x2, y2 = not_repeat_points[j][0], not_repeat_points[j][1]
dy, dx = y2 - y1, x2 - x1
# 求斜率
# 方式一 利用公约数
g = gcd(dy, dx)
if g != 0:
dy //= g
dx //= g
slope["{}/{}".format(dy, dx)] += points_dict[not_repeat_points[j]]
# --------------------
# 方式二, 利用除法(不准确, 速度快)
# if dx == 0:
# tmp = "#"
# else:
# tmp = dy * 1000 / dx * 1000
# slope[tmp] += points_dict[not_repeat_points[j]]
#------------------------------
res = max(res, max(slope.values()) + points_dict[not_repeat_points[i]])
return res