查找表
考虑的基本数据结构
第一类:查找有无——set
- 元素’a’是否存在,通常用set:集合
- set只存储键,而不需要对应其相应的值。
- set中的键不允许重复
第二类:查找对应关系(键值对应)——dict
- 元素’a’出现了几次:dict → \rightarrow →字典
- dict中的键不允许重复
第三类:改变映射关系——map
- 通过将原有序列的关系映射统一表示为其他
349.两个数组的交集1【简单】
LeetCode传送门
给定两个数组,编写一个函数来计算它们的交集。
说明:输出结果中的每个元素一定是唯一的。我们可以不考虑输出结果的顺序。
示例 :
- 输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
- 输出:[9,4]
思路1:
把nums1记录为set,判断nums2的元素是否在set中,是的话,就放在一个公共的set中,最后公共的set就是我们要的结果。
代码:
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1 = set(nums1)
return set([i for i in nums2 if i in nums1])
思路2:
通过set的内置方法来实现,直接求set的交集。
代码:
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
set1 = set(nums1)
set2 = set(nums2)
return set2 & set1 # 或 return set1.intersection(set2)
350.两个数组的交集2【简单】
LeetCode传送门
给定两个数组,编写一个函数来计算它们的交集。
说明:输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。我们可以不考虑输出结果的顺序。
示例:
- 输入:nums1 = [1,2,2,1], nums2 = [2,2]
- 输出:[2,2]
思路:
元素出现的次数有用,那么对于存储次数就是有意义的,所以选择数据结构时,就应该选择dict的结构,通过字典的比较来判断;记录每个元素的同时要记录这个元素的频次。
记录num1的字典,遍历nums2,比较nums1的字典的nums的key是否大于零,从而进行判断。
代码:
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
from collections import Counter
nums1_dict=Counter(nums1)
res=[]
for num in nums2:
if nums1_dict[num]>0:
res.append(num)
nums1_dict[num]-=1
return res
242.有效的字母异位词【简单】
LeetCode传送门
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1:
- 输入: s = “anagram”, t = “nagaram”
- 输出: true
思路:
判断异位词即判断变换位置后的字符串和原来是否相同,那么不仅需要存储元素,还需要记录元素的个数。可以选择dict的数据结构,将字符串s和t都用dict存储,而后直接比较两个dict是否相同。
代码:
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
from collections import Counter
s=Counter(s)
t=Counter(t)
if s==t:
return True
else:
return False
202.快乐数【简单】
LeetCode传送门
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
示例:
- 输入:19
- 输出:true
- 解释:
1 2 + 9 2 = 82 8 2 + 2 2 = 68 6 2 + 8 2 = 100 1 2 + 0 2 + 0 2 = 1 1^2 + 9^2 = 82\\ 8^2 + 2^2 = 68 6^2 + 8^2 = 100 1^2 + 0^2 + 0^2 = 1 12+92=8282+22=6862+82=10012+02+02=1
思路:
重复执行「将该数替换为它每个位置上的数字的平方和」这一过程,猜测会有以下三种可能:
- 最终会得到1;
- 最终会进入循环;
- 值会越来越大,最后接近无穷大。
第三个情况比较难以检测和处理。考虑各个位数的最大数字的下一位数是多少:
Digits | Largest | Next |
---|---|---|
1 | 9 | 81 |
2 | 99 | 162 |
3 | 999 | 243 |
4 | 9999 | 324 |
13 | 9999999999999 | 1053 |
对于 33 位数的数字,它不可能大于 243243。这意味着它要么被困在 243243 以下的循环内,要么跌到 11。44 位或 44 位以上的数字在每一步都会丢失一位,直到降到 33 位为止。所以我们知道,最坏的情况下,算法可能会在 243243 以下的所有数字上循环,然后回到它已经到过的一个循环或者回到 11。但它不会无限期地进行下去,所以我们排除第三种选择。
这样这道题的思路就很明显了,当 n 不等于 1 时就循环,每次循环时,将其最后一位到第一位的数依次平方求和,比较求和是否为1。那么这里也可以按此判断,因为只需要判断有或无,不需要记录次数,故用set的数据结构。每次对求和的数进行add,当新一次求和的值存在于set中时,就return false.
代码:
class Solution:
def isHappy(self, n: int) -> bool:
exists=set()
while n!=1:
sums=0
while n>0:
tmp=n%10
sums+=tmp**2
n//=10
if sums in exists:
return False
else:
exists.add(sums)
n=sums
return True
290.单词规律【简单】
LeetCode传送门
给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。
这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。
示例:
- 输入: pattern = “abba”, str = “dog cat cat dog”
- 输出: true
思路:
如何能既考虑顺序,也考虑
p
a
t
t
e
r
n
pattern
pattern 和
s
t
r
str
str 键值对应的关系呢?
抓住变与不变,变的是键,但是不变的是各个字典中,对应的相同 i n d e x index index 下的值,如 d i c t 1 [ i n d e x ] = d i c t 2 [ i n d e x ] dict1[index] = dict2[index] dict1[index]=dict2[index],那么我们可以创建两个新的字典,遍历index对两个新的字典赋值,并比较value。
还有一个思路比较巧妙,既然不同,那么可以考虑怎么让它们相同,将原来的dict通过map映射为相同的key,再比较相同key的dict是否相同。
代码:
注:map是通过hash存储的。
class Solution:
def wordPattern(self, pattern: str, str: str) -> bool:
str=str.split()
return list(map(pattern.index,pattern))==list(map(str.index,str))
205.同构字符串【简单】
LeetCode传送门
给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。
所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。你可以假设 s 和 t 具有相同的长度。
示例 1:
- 输入: s = “egg”, t = “add”
- 输出: true
示例 2:
- 输入:s = “foo”, t = “bar”
- 输出:false
思路:
可以考虑通过建两个dict,比较怎样不同,也可以将不同转化为相同。
代码:
class Solution:
def isIsomorphic(self, s: str, t: str) -> bool:
n=len(s)
st,ts={},{}
for i in range(n):
if s[i] in st:
if t[i]!=st[s[i]]:
return False
if t[i] in ts:
if s[i]!=ts[t[i]]:
return False
st[s[i]]=t[i]
ts[t[i]]=s[i]
return True
class Solution:
def isIsomorphic(self, s: str, t: str) -> bool:
return list(map(s.index,s)) == list(map(t.index,t))
451.根据字符出现频率排序【中等】
LeetCode传送门
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
示例 :
- 输入:“tree”
- 输出:“eert”
- 解释:'e’出现两次,'r’和’t’都只出现一次。因此’e’必须出现在’r’和’t’之前。此外,"eetr"也是一个有效的答案。
思路:
使用字典统计频率,对字典的value进行排序,最终根据key的字符串乘上value次数,组合在一起输出。
代码:
class Solution:
def frequencySort(self, s: str) -> str:
from collections import Counter
s_dict=Counter(s)
# sorted 返回的是列表元组
s=sorted(s_dict.items(),key=lambda item:item[1],reverse=True)
ans=''
for k,v in s:
ans+=k*v
return ans
对撞指针
当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O ( n 2 ) O(n^2) O(n2) 减少至 O ( n ) O(n) O(n)。
1.两数之和【简单】
LeetCode传送门
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
思路1: 暴力法
O
(
n
2
)
O(n^2)
O(n2)
第一遍遍历数组,第二遍遍历当前遍历值之后的元素,其和等于 target 则 return。
代码:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
n=len(nums)
for i in range(n):
for j in range(i+1,n):
if nums[i]+nums[j]==target:
return [i,j]
思路2: 排序+指针对撞
O
(
n
)
+
O
(
n
log
n
)
=
O
(
n
)
O(n)+O(n\log n)=O(n)
O(n)+O(nlogn)=O(n)
因为问题本身不是有序的,因此需要对原来的数组进行一次排序,排序后就可以用
O
(
n
)
O(n)
O(n)的指针对撞进行解决。
但是问题是,返回的是数字的索引,如果只是对数组的值进行排序,那么数组原来表示的索引的信息就会丢失,所以在排序前要进行些处理。
- 如果只是对数组的值进行排序,那么数组原来表示的索引的信息就会丢失的情况,可以在排序前:通过
list(enumerate(nums))
开始实现下标和值的绑定。
代码:
class Solution:
def twoSum(self,nums,target):
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:
r-=1
elif nums[l][1]+nums[r][1]<target:
l+=1
else:
return nums[l][0],nums[r][0]
思路3: 查找表
O
(
n
)
O(n)
O(n)
遍历数组过程中,当遍历到元素v时,可以只看v前面的元素,是否含有target-v的元素存在。
- 如果查找成功,就返回解;
- 如果没有查找成功,就把v放在查找表中,继续查找下一个解。
即使 v 放在了之前的查找表中覆盖了 v,也不影响当前 v 元素的查找。因为只需要找到两个元素,只需要找 target-v 的另一个元素即可。
代码:
class Solution:
def twoSum(self,nums,target):
record=dict()
for i in range(len(nums)):
complement=target-nums[i]
if record.get(complement) is not None:
return i,record[complement]
record[nums[i]]=i
15.三数之和【中等】
LeetCode传送门
给你一个包含 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\leq b\leq c
a≤b≤c,
保证只有 ( a , b , c ) (a,b,c) (a,b,c)这个顺序会被枚举到,而 ( b , a , c ) (b,a,c) (b,a,c)、 ( c , b , a ) (c,b,a) (c,b,a)等就不会被枚举到。要实现这一点,可以将数组中的元素按照从小到大进行排序。 - 对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。
举个例子,如果排完序的数组为[0, 1, 2, 2, 2, 3]
,使用三重循环枚举到的第一个三元组为 (0,1,2),如果第三重循环继续枚举下一个元素,那么仍然是三元组 (0,1,2),产生了重复。因此我们需要将第三重循环「跳到」下一个不相同的元素,即数组中的最后一个元素 33,枚举三元组 (0, 1, 3)(0,1,3)。 - 第二重循环和第三重循环实际上是并列的关系
如果我们固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 c 满足 a + b + c = 0 a+b+c=0 a+b+c=0。当第二重循环往后枚举一个元素 b ′ b' b′ 时,由于 b ′ > b b' > b b′>b,那么满足 a + b ′ + c ′ = 0 a+b'+c'=0 a+b′+c′=0 的 c ′ c' c′ 一定有 c ′ < c c' < c c′<c,即 c ′ c' c′ 在数组中一定出现在 c 的左侧。也就是说,我们可以从小到大枚举 b,同时从大到小枚举 c,即第二重循环和第三重循环实际上是并列的关系。可以使用对撞指针实现。
代码:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
ans=[]
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:
sums=nums[i]+nums[l]+nums[r]
if sums==0:
ans.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 sums<0:
l+=1
else:
r-=1
return ans
总结:
- 当数组不是有序时需要注意,有序的特点在哪里,有序就可以用哪些方法解决?无序的话不便在哪里?
- 处理重复值的套路:先转换为有序数组,再循环判断其与上一次值是否重复:
# 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.四数之和【中等】
LeetCode传送门
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。
思路:
4Sum可以当作是3Sum问题的扩展,注意事项仍是一样的,同样是不能返回重复值得解。首先排序。接着从
[
0
,
l
e
n
−
1
]
[0,len- 1]
[0,len−1]遍历
i
i
i,跳过
i
i
i 的重复元素,再在
[
i
+
1
,
l
e
n
−
1
]
[i+1,len-1]
[i+1,len−1] 中遍历
j
j
j,得到
i
,
j
i,j
i,j后,再选择首尾的
l
l
l 和
r
r
r,通过对撞指针的思路,四数和大的话
r
−
−
r--
r−−,小的话
l
+
+
l++
l++,相等的话纳入结果 list,最后返回。
另外,需要加些边界条件判断:当len小于4时,直接返回;当只有4个值且长度等于target时,直接返回本身即可。
代码:
Python标准库itertools.combinations:超出时间限制
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
from itertools import combinations
ans=set()
for i in combinations(nums,4):
if sum(i)==target:
ans.add(i)
return list(ans)
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
>> from itertools import combinations, permutations
>> permutations([1, 2, 3], 2)
<itertools.permutations at 0x7febfd880fc0>
>> list(permutations([1, 2, 3], 2))
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
>> list(combinations([1, 2, 3], 2))
[(1, 2), (1, 3), (2, 3)]
16.最接近的三数之和【中等】
LeetCode传送门
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
3
≤
n
u
m
s
.
l
e
n
g
t
h
≤
1
0
3
,
−
1
0
3
≤
n
u
m
s
[
i
]
≤
1
0
3
,
−
1
0
4
≤
t
a
r
g
e
t
≤
1
0
4
3\leq nums.length\leq 10^3,-10^3\leq nums[i]\leq 10^3,-10^4\leq target\leq 10^4
3≤nums.length≤103,−103≤nums[i]≤103,−104≤target≤104。
示例:
- 输入:nums = [-1,2,1,-4], target = 1
- 输出:2
- 解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
思路:
开始时可以随机设定一个三个数的和为结果值,在每次比较中,先判断三个数的和是否和target相等,如果相
等直接返回和。如果不相等,则判断三个数的和与target的差是否小于这个结果值时,如果小于则进行则进行替
换,并保存和的结果值。
代码:
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
ans=nums[0]+nums[1]+nums[2]
diff=abs(ans-target)
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)
ans=nums[i]+nums[l]+nums[r]
if nums[l]+nums[r]<t:
l+=1
else:
r-=1
return ans
454.四数相加2【中等】
LeetCode传送门
给定四个包含整数的数组列表 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 。
思路1:
O
(
n
3
)
O(n^3)
O(n3)
首先可以考虑把D数组中的元素都放入查找表,然后遍历前三个数组,判断target减去每个元素后的值是否在查找表中存在,存在的话,把结果值加1。那么查找表的数据结构选择用set还是dict?考虑到数组中可能存在重复的元素,而重复的元素属于不同的情况,因此用dict存储,最后的结果值加上dict相应key的value。
代码:
class Solution:
def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
from collections import Counter
record=Counter(D)
ans=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:
ans+=record[num_find]
return ans
思路2:
对于C和D的数组,可以通过dict来记录其中和的个数,之后遍历结果在和中进行查找
代码:
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
ans=0
for i in range(len(C)):
for j in range(len(D)):
num_find=0-C[i]-D[j]
if record.get(num_find)!=None:
ans+=record[num_find]
return ans
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)
49.字母异位词分组【中等】
LeetCode传送门
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
所有输入均为小写字母。不考虑答案输出的顺序。
示例:
- 输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
- 输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
思路:
题目中不变的条件:如果将字符串统一排序,异位词排序后的字符串,显然都是相同的。
可以把异位词排序后的字符串当作key,把遍历的数组中的异位词当作value,对字典进行赋值,进而遍历字典的value,得到结果list。
代码:
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
from collections import defaultdict
strs_dict=defaultdict(list)
ans=[]
for s in strs:
key=''.join(sorted(list(s)))
strs_dict[key]+=s.split(',')
for v in strs_dict.values():
ans.append(v)
return ans
defaultdict:字典的子类。
defaultdict与dict实例化字典类型的区别:使用defaultdict,任何未定义的key都会默认返回一个根据method_factory参数不同的默认值, 而相同情况下dict()会返回KeyError。
447.回旋镖的数量【简单】
LeetCode传送门
给定平面上 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]]
思路:
开始的思路就是三层遍历,i从0到len,j从i+1到len,k从j+1到len,然后比较三个点的距离,相等则结果数加一,这样的时间复杂度为
O
(
n
3
)
O(n^3)
O(n3)。考虑采用查找表的思路进行优化。
考虑在这道题中,可以通过查找表进行代替哪两层循环。当 i , j i,j i,j 两点距离等于 i , k i,k i,k 时,用查找表的思路,等价于:对距离 key (i,j或i,k的距离),其值 value (个数)为2。那么就可以做一个查找表,用来查找相同距离 key 的个数 value 是多少。遍历每一个节点i,扫描得到其他点到节点 i 的距离,在查找表中,对应的键就是距离的值,对应的值就是距离值得个数。在拿到对于元素i的距离查找表后,接下来就是排列选择问题了:当距离为 x x x 的值有 n ( n ≤ 2 ) n(n\leq2) n(n≤2) 个时,选择 j , k j,k j,k 的可能情况有:第一次选择有 n n n 种,第二次选择有 n − 1 n-1 n−1 种。
对于距离值的求算,按照欧式距离的方法进行求算的话,容易产生浮点数,可以将根号去掉,用差的平方和来进行比较距离。
代码:
class Solution:
def numberOfBoomerangs(self, points: List[List[int]]) -> int:
ans=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():
ans+=v*(v-1)
return ans
def dis(self,point1,point2):
return (point1[0]-point2[0])**2+(point1[1]-point2[1])**2
class Solution:
def numberOfBoomerangs(self, points: List[List[int]]) -> int:
from collections import Counter
def f(x1,y1):
d=Counter((x2-x1)**2+(y2-y1)**2 for x2,y2 in points)
return sum(t*(t-1) for t in d.values())
return sum(f(x1,y1) for x1,y1 in points)
149.直线上最多的点数【困难】
LeetCode传送门
给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。
示例:
- 输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
- 输出: 4
思路:
判断点是否在一条直线上,其实就等价于判断
i
,
j
i,j
i,j 两点的斜率是否等于
i
,
k
i,k
i,k 两点的斜率。
直接考虑使用查找表实现,即查找相同斜率 key 的个数 value 是多少。
- i 和 j,j 和 i 是两种相同的情况,在遍历每个 i,查找 i 所在直线上包含的最多点数,应返回查找表中的最大的value值。如果某个斜率出现 n 次,返回的应该是 n+1 个点(加上点 i),因为返回的是最大值,i 和 j、j 和 i 不会重复计算。
- 当有相同元素时,题目的要求是算作两个不同的点,但是在程序运行时,会将其考虑为相同的点,return回了inf。但在实际运行时,需要对相同元素的情况单独考虑(相同的点必定在同一条直线)。
- 可以设定samepoint值,遍历时判断,如果相同时,samepoint值++,最后取v+samepoint的值作为结果数。
- 考虑到如果全是相同值,那么这时dict中的record为空,也要将samepoint值当作结果数返回。
- 边界条件(无法计算斜率):如果点集为空,返回0,如果点集只有一个点,返回1。
- 计算斜率的方法:
- 除数:速度快,但不精确。
- 将除数化为分数最简形式(求最大公约数)。
代码:
class Solution:
def maxPoints(self, points: List[List[int]]) -> int:
if len(points)<=1:return len(points)
ans=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]==points[j]: # 包括j==i
samepoint+=1
else:
record[self.get_slope(points,i,j)]+=1
for v in record.values():
ans=max(ans,v+samepoint)
ans=max(ans,samepoint)
return ans
def get_slope(self,points,i,j):
from decimal import Decimal
if points[i][0]-points[j][0]==0:
return float('inf')
else:
return Decimal(str((points[i][1]-points[j][1])))/Decimal(str((points[i][0]-points[j][0])))
class Solution:
def maxPoints(self, points: List[List[int]]) -> int:
if len(points)<=1:return len(points)
ans=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]==points[j]: # 包括j==i
samepoint+=1
else:
record[self.get_slope(points,i,j)]+=1
for v in record.values():
ans=max(ans,v+samepoint)
ans=max(ans,samepoint)
return ans
def gcd(self,dy,dx):
if y==0:
return x
else:
return gcd(y,x%y)
def get_slope(self,points,i,j):
dy=points[i][1]-points[j][1]
dx=points[i][0]-points[j][0]
g=gcd(dy,dx)
if g!=0:
dy//=g
dx//=g
return '{}/{}'.format(dy,dx)
滑动数组
滑动窗口是数组/字符串问题中常用的抽象概念。 窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [𝑖,𝑗) 。滑动窗口是可以将两个边界向某一方向“滑动”的窗口。
3.无重复字符的最长子串【中等】
LeetCode传送门
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例:
- 输入: “abcabcbb”
- 输出: 3
- 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
思路1: 滑动窗口
使用 HashSet 将字符存储在当前窗口 [𝑖,𝑗) (最初 𝑗=𝑖 )中,然后向右侧滑动索引 𝑗 ,如果它不在 HashSet 中,我们会继续滑动 𝑗 ,直到 𝑠[𝑗] 已经存在于 HashSet 中。此时,找到的没有重复字符的最长子字符串将会以索引 𝑖 开头,如果对所有的 𝑖 这样做,就可以得到答案。
代码:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
n=len(s)
HashSet=set([])
ans,i,j=0,0,0
while i<n and j<n:
if s[j] not in HashSet:
HashSet.add(s[j])
j+=1
ans=max(ans,j-i)
else:
HashSet.remove(s[i])
i+=1
return ans
思路2: 哈希表优化的滑动窗口
上述的方法最多需要执行 2n 个步骤。事实上,它可以被进一步优化为仅需要 n 个步骤。定义字符到索引的映射,而不是使用集合来判断一个字符是否存在。当找到重复的字符时,可以立即跳过该窗口。也就是说,如果 𝑠[𝑗] 在 [𝑖,𝑗) 范围内有与 𝑗′ 重复的字符,不需要逐渐增加 𝑖 ,可以直接跳过 [𝑖,𝑗′] 范围内的所有元素,并将 𝑖 变为 𝑗′+1 。
代码:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
n=len(s)
ans,i=0,0
HashMap={}
for j in range(n):
if s[j] in HashMap:
i=max(HashMap.get(s[j],-1),i)#窗口左端变为j'+1(index)
ans=max(ans,j-i+1)# [j'+1,j]字符长度
HashMap[s[j]]=j+1# key为字符,value为j'+1
return ans
219.存在重复元素2【简单】
LeetCode传送门
给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。
思路:
固定滑动数组的长度为K+1,当这个滑动数组内如果能找到两个元素的值相等,就可以保证两个元素的索引的差是
小于等于k的。如果当前的滑动数组中没有元素相同,就右移滑动数组的右边界r,同时将左边界l右移。查看r++的元
素是否在l右移过后的数组里,如果不在就将其添加数组,在的话返回true表示两元素相等。
因为滑动数组中的元素是不同的,考虑用set作为数据结构。
代码:
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
record=set()
for i in range(len(nums)):
if nums[i] in record:
return True
record.add(nums[i])
if len(record)==k+1:
record.remove(nums[i-k])
return False
220.存在重复元素3【中等】
LeetCode传送门
在整数数组 nums 中,是否存在两个下标 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值小于等于 t ,且满足 i 和 j 的差的绝对值也小于等于 ķ 。如果存在则返回 true,不存在返回 false。
思路:
- 将索引的差值固定,转化为了固定长度K+1的滑动窗口内,是否存在两个值的差距不超过 t,考虑使用滑动窗口的思想来解决。
- 在遍历的过程中,目的是要在“已经出现、但还未滑出滑动窗口”的所有数中查找,是否有一个数与滑动数组中的数的差的绝对值最大为 t。对于差的绝对值最大为t,实际上等价于所要找的这个元素v的范围是在 v-t 到 v+t 之间,即查找“滑动数组”中的元素有没有范围内的数存在。因为只需证明是否存在即可,这时判断的逻辑是:如果在滑动数组查找比 v-t 大的最小的元素,如果这个元素小于等于 v+t,即可以证明存在 [v-t,v+t]。
- 如何查找比v-t大的最小的元素:滑动数组作为set,是有序的数组,对于有序的数组,考虑二分查找实现查找比 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)
ix=self.lower_bound(rec,nums[i]-t)
if ix!=-1 and rec[ix]<=nums[i]+t:
return True
record.add(nums[i])
if len(record)==k+1:
record.remove(nums[i-k])
return False
# 大于target的最小元素
def lower_bound(self,nums,target):
low,high=0,len(nums)-1
while low<high:
mid=(low+high)//2
if nums[mid]<target:
low=mid+1
else:
high=mid
return low if nums[low]>=target else -1
事实上,暴力遍历的运行时间要比上面的小,原因可能是上面的步骤中存在着大量的set和list的转换导致。
class Solution:
def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
if t==0 and len(nums)==len(set(nums)):
return False
for i in range(len(nums)):
for j in range(1,k+1):
if i+j>=len(nums):break
if abs(nums[i+j]-nums[i])<=t:return True
return False
二分查找
二分查找代码模板:
def binarySearch (arr, x):
n=len(arr)
low,high=0,n-1
while low<high:
mid=(low+high)//2
if f(x):
low=mid+1
else:
high=mid
return low
- low 和 high分别对应搜索的上界和下界,但不一定为0和arr 最后一个元素的下标。
- 比较重要的就是这个f(x),在带入模板的情况下,写对函数就完了。
- ±1 的位置调整只出现了一次,而且最后返回low还是high都是对的,无需纠结。
- 即使区间为空、答案不存在、有重复元素、搜索开/闭区间的上/下界也同样适用
35.搜索插入位置【简单】
LeetCode传送门
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
思路:
high 要设置为 len(nums) 的原因是target可能大于数组的最大值。
代码:
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
low,high=0,len(nums)
while low<high:
mid=(low+high)//2
if nums[mid]<target:
low=mid+1
else:
high=mid
return low
540.有序数组中的单一元素【中等】
LeetCode传送门
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
注意:您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。
思路:
- 数组长度为奇数,如果某个元素的下标为奇数,如果它不是只出现一次,它应和它的前一个元素(i-1)相等,如果元素的下标为偶数,如果它不是只出现一次,它应和它的后一个元素(i+1)相等。
- 异或的巧妙应用:如果mid是偶数,那么和1异或的话,那么得到的是mid+1,如果mid是奇数,得到的是mid-1。
代码:
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
low,high=0,len(nums)-1
while low<high:
mid=(low+high)//2
if nums[mid]==nums[mid^1]:
low=mid+1
else:
high=mid
return nums[low]
410.分割数组的最大值【困难】
LeetCode传送门
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。注意:数组长度 n 满足以下条件:1 ≤ n ≤ 1000,1 ≤ m ≤ min(50, n)
思路:
- 子数组各自和的最大值是有范围的,在区间 [ max ( n u m s ) , ∑ ( n u m s ) ] [\max (nums),\sum(nums)] [max(nums),∑(nums)] 之中。
- 使用二分查找在区间
[
max
(
n
u
m
s
)
,
∑
(
n
u
m
s
)
]
[\max (nums),\sum(nums)]
[max(nums),∑(nums)] 中寻找数组和的最大值:令
l
=
max
(
n
u
m
s
)
,
h
=
∑
(
n
u
m
s
)
,
m
i
d
=
(
l
+
h
)
/
/
2
l=\max (nums), h=\sum(nums),mid=(l+h)//2
l=max(nums),h=∑(nums),mid=(l+h)//2 ,计算数组和最大值不大于
m
i
d
mid
mid 对应的子数组个数
c
n
t
cnt
cnt,
- 如果 c n t > m cnt>m cnt>m,说明划分的子数组多了,即我们找到的 m i d mid mid 偏小。
- 否则,说明划分的子数组少了,即 m i d mid mid 偏大(或者正好就是目标值)。
代码:
class Solution:
def splitArray(self, nums: List[int], m: int) -> int:
def helper(mid):
ans=tmp=0
for num in nums:
if tmp+num<=mid:
tmp+=num
else:
ans+=1
tmp=num
return ans+1
low,high=max(nums),sum(nums)
while low<high:
mid=(low+high)//2
if helper(mid)>m:
low=mid+1
else:
high=mid
return low
参考
Datawhale社区开源教程之leetcode编程实践
LeetCode题解