哈希
1. 两数之和 - 力扣(LeetCode)
python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
d = {}
for i in range(len(nums)):
if nums[i] in d:
return [i,d[nums[i]]]
d[target-nums[i]] = i
return None
49. 字母异位词分组 - 力扣(LeetCode)
排序or计数
python
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
d = collections.defaultdict(list)
for s in strs:
mp = "".join(sorted(s))
d[mp].append(s)
return list(d.values())
时间复杂度:o(nklog(k))
空间复杂度:o(nk)
python
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
d = collections.defaultdict(list)
for s in strs:
counts = [0]*26
for c in s:
counts[ord(c)-ord('a')] += 1
# list不能作为key
d[tuple(counts)].append(s)
return list(d.values())
时间复杂度:o(nk)
空间复杂度:o(nk)
128. 最长连续序列 - 力扣(LeetCode)
python
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
# 如果出现重复元素,那么就需要我们不断的去重复判断,但是对于结果没有任何影响,所以需要先去重
# 如果去重以后元素能按照顺序排列,那么接下来就是判断起点和最大长度的过程
nums_set = sorted(set(nums))
n = len(nums_set)
if n<2:
return n
res = cur = 1
for i in range(1,n):
# if nums_set[i]-1!=nums_set[i-1]:
# res = max(res,cur) # 这样写的bug是如果全部连续,那么没有办法触发条件
# cur = 1
# else:
# cur += 1
if nums_set[i]-1==nums_set[i-1]:
cur += 1
res = max(res,cur)
else:
cur = 1
return res
时间复杂度:o(nlogn)
空间复杂度:o(n)
python
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
# 如果出现重复元素,那么就需要我们不断的去重复判断,但是对于结果没有任何影响,所以需要先去重
# 其实没必要排序,我们只要找到连续序列的起点开始计数即可
nums_set = set(nums)
n = len(nums_set)
if n<2:
return n
res = 1
for num in nums_set:
if num-1 not in nums_set:
cur = 1
while num+1 in nums_set:
cur += 1
num += 1
res = max(res,cur)
return res
时间复杂度:o(n) # 尽管for嵌套了while,但是由于if起点的判断,每一个元素在整体仅仅被遍历一次
空间复杂度:o(n)
双指针
283. 移动零 - 力扣(LeetCode)
python
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 快慢指针
# 慢指针指向非0元素的下一个元素
slow = fast = 0
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow],nums[fast] = nums[fast],nums[slow]
slow += 1
时间复杂度:o(n)
空间复杂度:o(1)
11. 盛最多水的容器 - 力扣(LeetCode)
python
class Solution:
def maxArea(self, height: List[int]) -> int:
# 左右指针,对于每个柱子,它能接的最多的雨水,就是距离它最远的柱子宽度乘以矮柱子的高度
# 所以矮柱子的移动是有可能带来整体的更多的雨水的!
left,right = 0,len(height)-1
res = 0
while left<right:
res = max(res,(right-left)*min(height[left],height[right]))
if height[left]<height[right]:
left += 1
else:
right -= 1
return res
时间复杂度:o(n)
空间复杂度:o(1)
15. 三数之和 - 力扣(LeetCode)
python
class Solution:
def threeSum(self, nums: list[int]) -> list[list[int]]:
res = []
# 注意题目要求不重复,所以必须排序,排序可以方便去重
nums_sort = sorted(nums)
n = len(nums_sort)
for f in range(n-2):
if f!=0 and nums_sort[f]==nums_sort[f-1]:
continue
# 三数之和等于0,那么first应该小于等于0
if nums_sort[f]>0:
break
s,t = f+1,n-1
while s<t:
cur = nums_sort[f]+nums_sort[s]+nums_sort[t]
if cur == 0:
res.append([nums_sort[f],nums_sort[s],nums_sort[t]])
# 去重
while s<t and nums_sort[s+1]==nums_sort[s]:
s += 1
while s<t and nums_sort[t-1]==nums_sort[t]:
t -= 1
# 去重后进位
s += 1
t -= 1
elif cur > 0:
t -= 1
else:
s += 1
return res
时间复杂度:o(n*n) # 最差就是内外都是遍历一遍
空间复杂度:o(logn) # 排序带来的,结果的存储不计入
42. 接雨水 - 力扣(LeetCode)
python
class Solution:
def trap(self, height: List[int]) -> int:
# 接雨水就用单调栈求解是最好的
# 柱子接多少雨水,完全取决于右侧柱子
# 一旦右侧柱子比当前高,那么当前柱子就能接雨水(接多少取决于左臂和右侧柱子)
n = len(height)
if n<2:
return 0
stack = []
res = 0
for i in range(n):
# 当前柱子高,stack[-1]就是坑底,stack[-2]才是左臂
# 按照现在的弹出顺序,整个栈一定是栈顶小的!stack[-1]<=stack[-2]
while stack and height[i]>height[stack[-1]]:
bottom = stack.pop()
if not stack:
break
left = stack[-1] # 左臂并不弹出
res += (i-left-1)*(min(height[left],height[i])-height[bottom])
stack.append(i)
return res
时间复杂度:O(n) # for里面嵌套while,但是while仅仅是作用于stack,每个元素入栈出栈一次最多
空间复杂度:O(n)
滑动窗口
3. 无重复字符的最长子串 - 力扣(LeetCode)
python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
# 快慢指针确定窗口,集合确定不重复,但是注意集合也是需要动态的,出现重复删除的是左侧
# 23245,到了2重复了,但是删的是左侧的2,而不是放弃全部窗口
# 223245,用while,而不是if
res = 0
d = set()
left = 0
for right in range(len(s)):
while s[right] in d:
d.remove(s[left])
left += 1
d.add(s[right])
res = max(res,len(d))
return res
时间复杂度:O(n) # for里面嵌套while,但是d也最多存一遍所有的字符
空间复杂度:O(n)
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
python
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
# 异位词的判断需要靠字典确定字符和数量
# 借助collections.Counter()库可以解决
# 但是我们不能对每一个字符串都这样去判断
# 仅仅判断第一个对应串,其他是一个增删的逻辑
m,n = len(s),len(p)
res = []
if m<n:
return res
ds = collections.Counter(s[:n])
dp = collections.Counter(p)
if ds == dp:
res.append(0)
# 构造窗口判断
slow = 0
for fast in range(n,m):
ds[s[fast]] += 1
ds[s[slow]] -= 1
# 下面的判断没有任何作用,仅仅是减少存储,不加完全可以
if ds[s[slow]] == 0: del ds[s[slow]]
slow += 1
if ds == dp:
res.append(slow)
return res
时间复杂度:O(n)
空间复杂度:O(n)
子串
560. 和为 K 的子数组 - 力扣(LeetCode)
python
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
# 求的是子数组,所以排序不可用
# 数字有正有负,所以快慢指针也会失效
# 求解思路是前缀和,如果i-j的子数组求和为k,说明j的前缀和减去i的前缀和为k
res = 0
pre = 0
d = {0:1} # 比如恰好每个元素都是k
for n in nums:
pre += n
if pre-k in d:
res += d[pre-k] # 可能不止一个
d[pre] = d.get(pre,0)+1
return res
时间复杂度:O(n)
空间复杂度:O(n)
239. 滑动窗口最大值 - 力扣(LeetCode)
python
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# 在滑动窗口中只要出现了最大值,那么滑动窗口里面就是确定的
# 但是如果原定的最大值离开了滑动窗口,则需要新的最大值
# 而这个过程满足的是先进先出
# 所以考虑一个维护顺序的队列
# 队列的队首是最大的元素,从队首到队尾,元素依次变小
# 队列里面存idx,需要判断长度是否达到滑动窗口
dq = []
res = []
for i in range(k):
# 不是所有的元素都存,而是说大元素之前的小元素没必要存
# 但是大元素之后的小元素是需要存的
# 所以从队尾开始往前走,只要大,就弹出前面的
# 但是从队尾开始,如果小,那就得存起来
while dq and nums[dq[-1]]<nums[i]:
dq.pop()
dq.append(i)
res.append(nums[dq[0]])
for i in range(k,len(nums)):
while dq and nums[dq[-1]]<nums[i]:
dq.pop()
dq.append(i)
if i-dq[0]>=k:
dq.pop(0)
res.append(nums[dq[0]])
return res
时间复杂度:O(n) # for里面嵌套while,但是d也最多存一遍所有的字符
空间复杂度:O(k)
76. 最小覆盖子串 - 力扣(LeetCode)
python
class Solution:
def minWindow(self, s: str, t: str) -> str:
# 其实还是异位词的思路,但是是子数组,所以对前后有点限制
m,n = len(s),len(t)
if m<n:
return ""
ds = collections.Counter()
dt = collections.Counter(t)
min_len = m+1
min_left = left = 0
cur = 0 # 记录一下有多少个字符满足了
required = len(dt) # 需要满足这些
for right in range(m):
ds[s[right]] += 1
# 看看是否可以满足一个
if s[right] in dt and ds[s[right]]==dt[s[right]]:
cur += 1
# 假如我们现在已经全部满足了,那就尝试缩小左边界,看看能不能更小
# 注意满足条件是cur = required
# 一次一步,边收缩边更新状态
while cur == required:
if right-left+1 < min_len:
min_left = left
min_len = right - left + 1
# 然后把left移出窗口,其实就是+=1,但是在这之前要判断是否破坏了成立性
if s[left] in dt:
ds[s[left]] -= 1
if ds[s[left]] < dt[s[left]]:
cur -= 1
left += 1
return "" if min_len == m+1 else s[min_left:min_left+min_len]
时间复杂度:O(n+m) # for里面嵌套while,但是实际一个移动right,一个移动left,各自其实均只有一遍,再加上构造counter的n。
空间复杂度:O(|Σ|),其中 Σ 是字符集。