查找2
对撞指针
1. 两数之和
1. 两数之和(简单)
方法一: 暴力法
遍历数组
再遍历该元素前的所有元素(此处可以优化)
方法二:
借助字典保存遍历过的元素及其下标。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
if not nums:
return [-1,-1]
visited = {}
for i,num in enumerate(nums):
t = target-num
if t in visited:
return [i,visited[t]]
visited[num] = i
return [-1,-1]
复杂度分析:
时间复杂度:O(N),最坏情况下遍历一遍数组
空间复杂度:O(N),字典
方法三: 排序+对撞指针
如果数组是有序的,就可以采用对撞指针求解;此处需要用到下标,因此排序前需要对下标和元素的对应关系进行存储。
通过enumerate来把索引和值进行绑定,进而对value进行sort,前后对撞指针进行返回。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
nums = list(enumerate(nums))
nums.sort(key=lambda x:x[1])
l,r = 0,len(nums)-1
while l<r:
if nums[l][1]+nums[r][1]==target:
return nums[l][0],nums[r][0]
elif nums[l][1]+nums[r][1]>target:
r -= 1
else:
l += 1
return []
15. 三数之和
15. 三数之和(中等)
审题:
1、数组无序
2、不能包含重复的三元组(值重复)
3、多个解的顺序不需要考虑
4、没有解时返回空列表
思路:
固定一个元素,对之后的元素可以用2sum的思路来解决——会出现重复元素
考虑排序(要求返回值,不需要考虑下标对应的问题)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
for i in range(len(nums)-2):
if nums[i]>0:
return res #如果最小值都大于0了,就肯定不存在三个数和为0
if i>0 and nums[i]==nums[i-1]: #去重复值
continue
#对撞指针
l = i+1
r = len(nums)-1
while l<r:
S = nums[i]+nums[l]+nums[r]
if S==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 S>0:
r -= 1
else:
l += 1
return res
Tips: 处理重复值的套路:先转换为有序数组,再循环判断其与上一次值是否重复:
# 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
18. 四数之和
18. 四数之和(中等)
与3Sum思路类似
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
res = []
n = len(nums)
for i in range(n-3):
if nums[i]*4>target:
break
if i>0 and nums[i]==nums[i-1]:
continue
for j in range(i+1,n-2):
if (nums[i]+nums[j])*2>target:
break
if j>i+1 and nums[j]==nums[j-1]:#注意j>i+1
continue
l = j+1
r = n-1
while l<r:
S = nums[i]+nums[j]+nums[l]+nums[r]
if S==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 S>target:
r -= 1
else:
l += 1
return res
16. 最接近的三数之和
16. 最接近的三数之和(中等)
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()#排序后才能使用对撞指针
n = len(nums)
dis = 9999#记录最小的差
num = 9999#记录差最小的和,即返回值
for i in range(n):
l = i+1
r = n-1
while l<r:
S = nums[i]+nums[l]+nums[r]
if S==target:
return target
elif S>target:
if S-target<dis:
dis = S-target
num = S
r -= 1
else:
if target-S<dis:
dis = target-S
num = S
l += 1
return num
其他
454. 四数相加 II(查找表)
454. 四数相加 II(中等)
方法一: 查找表
时间复杂度:O(N3)
超时
class Solution:
def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
from collections import Counter
dic = Counter(D)
res = 0
for a in A:
for b in B:
for c in C:
t = 0-a-b-c
if t in dic:
res += dic[t]
return res
方法二:
把数组A和数组B中能得到的所有和都放入查找表
时间复杂度可以优化为O(N2)
class Solution:
def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
dic = {}
for c in C:
for d in D:
t = c+d
if t in dic:
dic[t] += 1
else:
dic[t] = 1
res = 0
for a in A:
for b in B:
t = 0-a-b
if t in dic:
res += dic[t]
return res
49. 字母异位词分组(字典)
49. 字母异位词分组(中等)
对于异位字符串
1、异位字符串中包含的字符串的字母个数都是相同的;
2、异位词排序后的字符串也是相同的。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
dic = {}
for word in strs:
key = ''.join(sorted(word))#sorted(word):list, key:str
if key in dic:
dic[key].append(word)
else:
dic[key]=[word]
return list(dic.values())
447. 回旋镖的数量(查找表)
447. 回旋镖的数量(简单)
距离: 对于距离值的求算,按照欧式距离的方法进行求算的话,容易产生浮点数,可以将根号去掉,用差的平方和来进行比较距离。
查找表: i,j两点距离等于i,k时,等价于:对key(i,j或i,k的距离),其值为2
做一个查找表,查找距离key的个数value是多少
在拿到对于元素i的距离查找表后,接下来就是排列选择问题了:
1、如果当距离为x的值有2个时,那么选择j,k的可能情况有:第一次选择有2种,第二次选择有1种,为21;
2、如果当距离为x的值有3个时,那么选择j,k的可能的情况有:第一次选择有3种,第二次选择有2种,为32;
3、那么当距离为x的值有n个时,选择j,k的可能情况有:第一次选择有n种,第二次选择有n-1种,为n*(n-1)。
class Solution:
def numberOfBoomerangs(self, points: List[List[int]]) -> int:
res = 0
n = len(points)
for i in range(n):
dic = {}#对于元素i的距离查找表
for j in range(n):
if i!=j:
d = self.dis(points[i],points[j])
if d in dic:
dic[d] += 1
else:
dic[d] = 1
for k,v in dic.items():
res += v*(v-1)
return res
def dis(self,p1,p2):
return (p1[0]-p2[0])**2+(p1[1]-p2[1])**2
149. 直线上最多的点数(查找表)
149. 直线上最多的点数(困难)
与447类似,不同之处在于:
查找相同斜率key的个数value是多少
- 除数,不够精确(eg.[[0,0],[94911150,94911151],[94911151,94911152]])
- 用最大约数方法(gcd), 把他化成最简形式, 3/6 = 2/4 = 1/2
取max,+1
注意一些细节问题:
1、求斜率时,存在分母为0的情况,需要额外考虑;
2、记录重复的点,最后取v+same的值作为结果数;全是相同值,返回same
3、边界情况,两点确定一条直线,因此数组长度小于3时,返回数组长度即可
class Solution:
def maxPoints(self,points):
#求最大公约数
def gcd(x,y):
if y==0:
return x
else:
return gcd(y,x%y)
if len(points) <= 1:
return len(points)
res = 0
from collections import defaultdict
for i in range(len(points)):
slope = 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:
dx = points[i][0] - points[j][0]
dy = points[i][1] - points[j][1]
g = gcd(dy,dx)
if g!=0:
dy //= g
dx //= g
slope["{}/{}".format(dy, dx)] += 1
else:
slope['a'] += 1#dx=0的情况
for v in slope.values():
res = max(res, v+samepoint)
res = max(res, samepoint)#全部都是相同点的情况
return res
滑动窗口
滑动窗口代码框架(来自labuladong)
int left = 0, right = 0;
while (right < s.size()) {
window.add(s[right]);
right++;
while (valid) {
window.remove(s[left]);
left++;
}
}
219. 存在重复元素 II
219. 存在重复元素 II(简单)
暴力解法双层循环,用查找表优化为一重循环
方法一: 查找表
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
seen = {}
for i in range(len(nums)):
if nums[i] not in seen:
seen[nums[i]] = i
else:
if i-seen[nums[i]]<=k:
return True
else:
seen[nums[i]] = i
return False
复杂度分析:
时间复杂度:O(N)
空间复杂度:O(N)
方法二: 滑动窗口
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
seen = set()
l,r = 0,0
while r<len(nums):
if nums[r] in seen:
return True
seen.add(nums[r])
r += 1
while r-l>k:
seen.remove(nums[l])
l += 1
return False
复杂度分析:
时间复杂度:O(N)
空间复杂度:O(N)
220. 存在重复元素 III
220. 存在重复元素 III(中等)
方法一: 滑动窗口
滑动窗口的长度<=K+1
在滑动数组中查找在[v-t,v+t]范围内的数 -> 查找比v-t大的最小元素,判断它是否小于等于v+t
class Solution:
def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
record = set()
for i in range(len(nums)):
if len(record)!=0:
rec = list(record)
idx = self.lower_bound(rec,nums[i]-t)
if idx!=-1 and rec[idx]<=nums[i]+t:
return True
record.add(nums[i])
if len(record)==k+1:
record.remove(nums[i-k])
return False
def lower_bound(self,nums,target):
lo = 0
hi = len(nums)-1
while lo<hi:
mid = (lo+hi)//2
if nums[mid]<target:
lo = mid+1
else:
hi = mid
return lo if nums[lo]>=target else -1
复杂度分析:
时间复杂度:O(nlogn)
空间复杂度:O(n)
方法二: 桶排序
class Solution:
def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
if t<0 or k<0:
return False
buckets = {}
buckets_size = t+1
for i in range(len(nums)):
b = nums[i]//buckets_size
if b in buckets:#在同一个桶里,满足差值小于等于t的要求
return True
buckets[b] = nums[i]
if (b-1) in buckets and abs(buckets[b-1]-nums[i])<=t:
return True
if (b+1) in buckets and abs(buckets[b+1]-nums[i])<=t:
return True
#保证buckes里的元素都满足下标的要求
if i>=k:
buckets.pop(nums[i-k]//buckets_size)
return False
复杂度分析:
时间复杂度:O(N)
空间复杂度:O(min(n,k))
76. 最小覆盖子串
76. 最小覆盖子串(困难)
class Solution:
def minWindow(self, s: str, t: str) -> str:
if not s or not t:
return ""
min_Len = len(s)+1
min_l = 0
need = {}
match = 0
for c in t:
if c in need:
need[c] += 1
else:
need[c] = 1
l,r = 0,0
while r<len(s):
c1 = s[r]
if c1 in need:
need[c1]-=1
if need[c1]==0:
match += 1
r += 1
while match==len(need):
c2 = s[l]
if min_Len>r-l:
min_Len = r-l
min_l = l
if c2 in need:
need[c2]+=1
if need[c2]>0:
match -= 1
l += 1
if min_Len == len(s)+1:#不存在这样的子串,返回“”
return ""
return s[min_l:min_l+min_Len]
参考资料:
https://github.com/datawhalechina/team-learning-program/blob/master/LeetCodeClassification/3.%E6%9F%A5%E6%89%BE.md