35. 搜索插入位置
202. 快乐数
205. 同构字符串
242. 有效的字母异位词
290. 单词规律
349. 两个数组的交集
350. 两个数组的交集 II
410. 分割数组的最大值
451. 根据字符出现频率排序
540. 有序数组中的单一元素
1. 两数之和
15. 三数之和
16. 最接近的三数之和
18. 四数之和
49. 字母异位词分组
149. 直线上最多的点数
219. 存在重复元素 II
220. 存在重复元素 III
447. 回旋镖的数量
454. 四数相加 II
难度 简单
题目:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
思路:
beg
和 end
逐渐向中间靠拢。beg
和 end
相遇,仍未找到目标值,那插入的位置即为 beg。class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
# 定义两个数组的指针
beg = 0
end = len(nums) - 1
# 使用二分法遍历数组
while beg <= end:
mid = (beg + end) // 2
if target > nums[mid]:
# 当target大于nums[mid]时,更新beg指针
beg = mid + 1
elif target < nums[mid]:
# 当target小于nums[mid]时,更新end指针
end = mid - 1
else:
# 当target等于nums[mid]时,返回目标值索引
return mid
# 遍历结束未找到目标值时,需要插入的位置保存在beg指针中,直接返回即可
return beg
复杂度分析:
难度 简单
题目:编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
如果 n
是快乐数就返回 True
;不是,则返回 False
。
思路:
class Solution:
def isHappy(self, n: int) -> bool:
"""集合法"""
s = set() # 保存中间结果
# n等于1,或n在集合s中时,退出循环
while n != 1 and n not in s:
s.add(n)
n = sum(map(lambda x: int(x) ** 2, str(n))) # 数位分离,求平方和
return n == 1
复杂度分析:
思路:
class Solution:
def isHappy(self, n: int) -> bool:
"""快慢指针法"""
def inner(m):
return sum(map(lambda x: int(x) ** 2, str(m))) # 数位分离,求平方和
# 初始化两个指针
slow = n # 慢指针每次前进1个节点
fast = inner(n) # 快指针每次前进2个节点
# 若慢指针与快指针在一个节点相遇,说明存在循环,否则快指针将率先到达数字1
while fast != 1 and slow != fast:
slow = inner(slow)
fast = inner(inner(fast))
return fast == 1
复杂度分析:
难度 简单
题目:给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。
所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。
思路:
class Solution:
def isIsomorphic(self, s: str, t: str) -> bool:
d = dict() # 保存映射关系
for i in range(len(s)):
if s[i] in d:
if d[s[i]] != t[i]:
return False # 若s[i]与t[i]的映射关系不匹配,则返回False
else:
if t[i] in d.values():
return False # 若s[i]不存在映射关系,而t[i]存在映射关系,则返回False
d[s[i]] = t[i] # 若s[i]与t[i]之间不存在映射关系,则添加
return True
复杂度分析:
难度 简单
题目:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
思路:
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
# 若两字符串长度不一致,则返回False
if len(s) != len(t):
return False
# 遍历s,统计字符出现次数
d = dict()
for i in s:
if d.get(i):
d[i] += 1
else:
d[i] = 1
# 遍历t,使对应字符的出现次数递减
for i in t:
if d.get(i) is None:
return False # 若t中的字符i不在字典d中,则返回False
d[i] -= 1
if d.get(i) < 0:
return False # 若t中的字符i使字典中i的出现次数递减至0以下,则返回False
return True
复杂度分析:
难度 简单
题目:给定一种规律 pattern
和一个字符串 str
,判断 str
是否遵循相同的规律。
这里的 遵循 指完全匹配,例如, pattern
里的每个字母和字符串 str
中的每个非空单词之间存在着双向连接的对应规律。
思路:
pattern
的字符长度与 str
的非空单词长度是否相同。pattern
中的字符与 str
中的非空单词之间的映射关系。class Solution:
def wordPattern(self, pattern: str, str: str) -> bool:
# 若两者的长度不一致,则返回False
if len(pattern) != len(str.split()):
return False
d = dict() # 保存映射关系
str = str.split()
for i in range(len(pattern)):
if pattern[i] in d:
if d[pattern[i]] != str[i]:
return False # 若pattern[i]与str[i]的映射关系不匹配,则返回False
else:
if str[i] in d.values():
return False # 若pattern[i]不存在映射关系,而str[i]存在映射关系,则返回False
d[pattern[i]] = str[i] # 若pattern[i]与str[i]之间不存在映射关系,则添加
return True
复杂度分析:
难度 简单
题目:给定两个数组,编写一个函数来计算它们的交集。
思路:
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
复杂度分析:
M
和 N
是数组的长度。将 nums1
转换为集合需要的时间,类似地,将 nums2
转换为集合需要的时间。最坏情况下是。难度 简单
题目:给定两个数组,编写一个函数来计算它们的交集。
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
可以不考虑输出结果的顺序。
思路:
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 先遍历较短的数组可以优化空间复杂度
if len(nums1) > len(nums2):
return self.intersect(nums2, nums1)
res = list() # 保存结果
d = dict() # 记录nums1中出现的数字以及出现次数
for i in nums1:
if d.get(i):
d[i] += 1
else:
d[i] = 1
for i in nums2:
# 若d中存在数字i,且i的出现次数大于0时,添加到res中,并令出现次数减1
# 若d中的i对应的值减至0,则将其键值对移除字典
if d.get(i):
res.append(i)
d[i] -= 1
if d[i] == 0:
d.pop(i)
return res
复杂度分析:
难度 困难
题目:给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
思路:
class Solution:
def splitArray(self, nums: List[int], m: int) -> int:
def inner(x):
# 计算数组和的最大值不超过x时所对应的子数组的数量count
sum_, count = 0, 1
for num in nums:
if sum_ + num > x:
count += 1
sum_ = num
else:
sum_ += num
return count > m
left = max(nums) # 子数组的最大值的下界
right = sum(nums) # 子数组的最大值的上界
# 二分查找
while left < right:
mid = (left + right) // 2
if inner(mid):
left = mid + 1 # 划分的子数组多了,mid偏小
else:
right = mid # 划分的子数组少了,mid偏大(或正好)
return left
复杂度分析:
难度 中等
题目:给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
思路:
from collections import Counter
class Solution:
def frequencySort(self, s: str) -> str:
return ''.join([k * v for k, v in Counter(s).most_common()])
难度 中等
题目:给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
思路:
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
# 初始化首尾指针
left = 0
right = len(nums) - 1
# 二分法
while left < right:
mid = (left + right) // 2
is_even = (right - mid) % 2 == 0 # 判断右子数组是否为偶数
if nums[mid] == nums[mid-1]: # mid处的元素与其前一位相同
if is_even:
# 此时,右子数组为偶数,应该在左子数组内搜索
right = mid - 2
else:
# 此时,右子数组为奇数,应该在右子数组内搜索
left = mid + 1
elif nums[mid] == nums[mid+1]: # mid处的元素与其后一位相同
if is_even:
# 此时,右子数组为偶数,但减去mid+1处的元素后为奇数,所以应该在右子数组内搜索
left = mid + 2
else:
# 此时,右子数组为奇数,但减去mid+1处的元素后为偶数,所以应该在左子数组内搜索
right = mid - 1
else:
# mid处的元素与其前后元素都不相同,则直接返回
return nums[mid]
return nums[left]
复杂度分析:
思路:
mid
是偶数,如果为奇数,则将其减 1
。mid
的元素是否与其后面的索引处元素相同。若相同,则我们知道 mid
不是单个元素。且单个元素在 mid
之后。若不相同,则我们知道单个元素位于 mid
,或者在 mid
之前。class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
# 初始化首尾指针
left = 0
right = len(nums) - 1
# 二分法
while left < right:
mid = (left + right) // 2
# 若mid为奇数时,减1使其为偶数
if mid % 2 == 1:
mid -= 1
if nums[mid] == nums[mid+1]:
# 若mid处元素与其后一位元素相同,则单一元素在右子数组内
left = mid + 2
else:
# 若mid处元素与其后一位元素不相同,则单一元素在左子数组内,或mid处元素即为单一元素
right = mid
return nums[left]
复杂度分析:
难度 简单
题目:给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
思路:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
d = dict() # 保存nums中的元素-索引对
for i in range(len(nums)):
x = target - nums[i]
if d.get(x) is not None:
return [i, d[x]] # 若d中存在x,则直接返回索引
d[nums[i]] = i # 若d中不存在x,则添加
return [] # 若不存在两数之和等于目标值,则返回空列表
复杂度分析:
难度 中等
题目:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
思路:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
nums.sort() # 排序
res = list() # 保存符合条件的结果
# 枚举a
for a in range(n):
# 枚举的a不能与上次相同
if a > 0 and nums[a] == nums[a-1]:
continue
c = n - 1 # 初始化c的指针
target = -nums[a]
# 枚举b
for b in range(a+1, n):
# 枚举的b不能与上次相同
if b > a+1 and nums[b] == nums[b-1]:
continue
# 需要保证b指针始终处于c指针的左侧
while b < c and nums[b] + nums[c] > target:
c -= 1
# 若指针重合,则表示没有合适的b,c指针使等式a+b+c=0成立
if b == c:
break
# 若等式a+b+c=0成立,则将符合条件的三元组添加至res
if nums[b] + nums[c] == target:
res.append([nums[a], nums[b], nums[c]])
return res
复杂度分析:
难度 中等
题目:给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
思路:
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
n = len(nums)
nums.sort() # 排序
res = 10 ** 4 # 保存最接近target的三数之和
# 枚举a
for a in range(n):
# 枚举的a不能与上次相同
if a > 0 and nums[a] == nums[a-1]:
continue
b, c = a + 1, n - 1 # 初始化b和c的指针
while b < c:
sum_ = nums[a] + nums[b] + nums[c] # 求三数之和
# 若三数之和等于target,则直接返回
if sum_ == target:
return sum_
# 更新最接近target的三数之和
if abs(sum_ - target) < abs(res - target):
res = sum_
if sum_ > target:
# 若三数之和大于target,则向左移动c的指针
c0 = c - 1
# 枚举的c不能与上次相同
while c0 > b and nums[c0] == nums[c]:
c0 -= 1
c = c0
else:
# 若三数之和小于target,则向右移动b的指针
b0 = b + 1
# 枚举的b不能与上次相同
while b0 < c and nums[b] == nums[b0]:
b0 += 1
b = b0
# 返回最接近target的三数之和
return res
复杂度分析:
难度 中等
题目:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
思路:
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
n = len(nums)
nums.sort() # 排序
res = list() # 保存所有符合条件的4个元素
# 枚举a
for a in range(n):
# 枚举的a不能与上次相同
if a > 0 and nums[a] == nums[a-1]:
continue
# 枚举b
for b in range(a+1, n):
# 枚举的b不能与上次相同
if b > a+1 and nums[b] == nums[b-1]:
continue
c, d = b + 1, n - 1 # 初始化c和d的指针
while c < d:
sum_ = nums[a] + nums[b] + nums[c] + nums[d] # 求四数之和
# 若四数之和等于target,则将其添加至res,并移动c和d的指针寻找下一个符合条件的元素
if sum_ == target:
res.append([nums[a], nums[b], nums[c], nums[d]])
# 确保枚举的c与上次不同
c0 = c + 1
while c0 < d and nums[c] == nums[c0]:
c0 += 1
c = c0
# 确保枚举的d与上次不同
d0 = d - 1
while d0 > c and nums[d0] == nums[d]:
d0 -= 1
d = d0
elif sum_ > target:
d -= 1 # 若四数之和大于target,则向左移动d的指针
else:
c += 1 # 若四数之和小于target,则向右移动d的指针
# 返回所有符合条件的四元素
return res
复杂度分析:
难度 中等
题目:给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
思路:
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
res = dict() # 保存计数-字母异位词对
for s in strs:
tmp = [0] * 26 # 计数初始化
# 统计字符出现次数
for c in s:
tmp[ord(c) - ord('a')] += 1
# 将统计结果添加至res字典
tmp = tuple(tmp)
if res.get(tmp):
res[tmp].append(s)
else:
res[tmp] = [s]
return list(res.values())
复杂度分析:
strs
的长度,而 K 是 strs
中字符串的最大长度。计算每个字符串的字符串大小是线性的,我们统计每个字符串。res
中的全部信息内容。难度 困难
题目:给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。
思路:
from collections import Counter
class Solution:
def maxPoints(self, points: List[List[int]]) -> int:
# 当点少于3个,则必定能构成直线,直接返回即可
if len(points) < 3:
return len(points)
# 求斜率,注意分母为0的情况
def slope(point1, point2):
return float('Inf') if point1[1] - point2[1] == 0 else (point1[0] - point2[0]) * 1000 / (point1[1] - point2[1]) * 1000
max_points = 0 # 记录同一直线上最多的点数
# 遍历每个点
for point in points:
# 记录与点point相同的点的数量
same = sum(1 for other_point in points if point == other_point)
# 计算point与其他不相同的点的斜率,并统计斜率的出现次数
count = Counter([slope(point, other_point) for other_point in points if point != other_point])
# 记录同一直线上与点point不相同的点的数量
diff = count.most_common(1)[0][1] if count else 0
# same + diff表示当前point点所在直线上最多的点
# 更新max_points
max_points = max(same + diff, max_points)
return max_points
复杂度分析:
points
的长度。我们需要使用两重循环来计算任意两点之间的斜率。难度 简单
题目:给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。
思路:
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
s = set() # 用于维持k大小的滑动窗口
# 遍历nums
for i in range(len(nums)):
# 若当前元素存在于滑动窗口内,则直接返回
if nums[i] in s:
return True
# 将当前元素添加到集合s
s.add(nums[i])
# 若滑动窗口的大小超过k,则移除最先添加的元素,维持窗口的大小为k
if len(s) > k:
s.remove(nums[i-k])
return False
复杂度分析:
nums
的长度。我们执行了一重循环来遍历 nums。难度 中等
题目:在整数数组 nums 中,是否存在两个下标 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值小于等于 t ,且满足 i 和 j 的差的绝对值也小于等于 ķ 。
如果存在则返回 true,不存在返回 false。
思路:
class Solution:
def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
# 两数之差的绝对值肯定不会小于0
if t < 0:
return False
d = dict() # 保存桶号-元素对
for i in range(len(nums)):
idx = nums[i] // (t + 1) # 计算nums[i]的桶号,其中t+1表示桶的大小
if idx in d:
# 若idx号桶已经存在元素,表示桶内元素与nums[i]的差的绝对值肯定小于等于t
return True
elif idx - 1 in d and abs(nums[i] - d[idx-1]) <= t:
# 若idx-1号桶内的元素与nums[i]的差的绝对值小于等于t,则返回True
return True
elif idx + 1 in d and abs(nums[i] - d[idx+1]) <= t:
# 若idx+1号桶内的元素与nums[i]的差的绝对值小于等于t,则返回True
return True
d[idx] = nums[i] # 将nums[i]添加至idx号桶
if i >= k:
# 维持大小为k的滑动窗口
d.pop(nums[i-k] // (t + 1))
return False
复杂度分析:
nums
的长度。对于这 N 个元素中的任意一个元素来说,我们最多只需要对字典进行三次搜索,一次插入和一次删除即可,而这些操作是常量时间复杂度的。难度 简单
题目:给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。
找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。
思路:
from collections import defaultdict
class Solution:
def numberOfBoomerangs(self, points: List[List[int]]) -> int:
# 计算两点之间的距离,此处省略了根号运算(省略后对结果没有什么影响)
def distance(x, y):
return (x[0] - y[0]) ** 2 + (x[1] - y[1]) ** 2
d = defaultdict(int) # 统计距离出现的次数
res = 0 # 用于将所有可能的情况累加起来
for i in points:
d.clear() # 清空字典
for j in points:
# 若遍历到同一个点,则跳过
if i == j:
continue
d[distance(i, j)] += 1 # 计算距离,并将其添加到字典中
# 遍历字典的值,累加结果
for v in d.values():
if v > 1: # 距离的出现次数大于1才有可能构成回旋镖
res += v * (v - 1)
return res # 返回累加结果
复杂度分析:
points
的长度。我们需要二重循环来计算两点之间的距离。难度 中等
题目:给定四个包含整数的数组列表 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 。所有整数的范围在到之间,最终结果不会超过。
思路:
from collections import Counter
class Solution:
def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
# 统计A、B中两两元素之和出现的次数
count = Counter(a+b for a in A for b in B)
# 将使等式A[i] + B[j] + C[k] + D[l] = 0成立的结果进行累加
return sum(count[-c-d] for c in C for d in D)
复杂度分析: