1.字母异位词分组
- 思路:字母异位词 排序后是相同的
- 排序后的字符串当作key
- value初始化为列表 且每次添加的是字符串
- 代码链接
- collections.defaultdict(list)
class Solution(object):
def groupAnagrams(self, strs):
"""
:type strs: List[str]
:rtype: List[List[str]]
"""
# 创建一个字典,且value默认是列表型
res = collections.defaultdict(list)
for st in strs:
key = "".join(sorted(st))
res[key].append(st)
return res.values()
2.最长连续序列
- 思路:去重集合,找的是nums[i-1]是否在集合中,如果在跳过,否则此时的元素就是该序列的开头,然后依次判断nums[i+1]是否在集合中,再则++
- 代码链接
class Solution(object):
def longestConsecutive(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
# 时间复杂度 O(n) 因为每个元素只进内层循环1次
longest_streak = 0
nums_set = set(nums) # 对列表进行去重
for num in nums_set:
if num - 1 not in nums_set:
current_num = num
current_streak = 1
else:
continue # 不加就无法通过
while current_num + 1 in nums_set:
current_num = current_num + 1
current_streak = current_streak + 1
longest_streak = max(longest_streak, current_streak)
return longest_streak
3.移动零
- 双指针,用一个指针i遍历数组,一个指针j来指代非零的index,如果nums[i]不是0,则交换nums[i]和nums[j],这样做相当于确保j指代0的位置,因为nums[i] = 0 的时候,j不移动,只有在i非0的时候才动,这样j会指向可以交换的位置
- 代码链接
class Solution(object):
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
if len(nums) == 1:
return nums
j = 0
for i in range(0,len(nums)):
if nums[i] != 0 :
nums[i],nums[j] = nums[j],nums[i]
j += 1
return nums
4.承最多水的容器
- 思路:明白体积公式 s = min(h[i],h[j]) *(j-i)
- 双指针,初始化一个在0,一个在末尾,处于一个宽最大的地方,然后比较两边高度,只保留高较大的,另一边向内缩小,面积才可能变大,反之,一定会变小。
- 代码链接
class Solution(object):
def maxArea(self, height):
"""
:type height: List[int]
:rtype: int
"""
# 时间复杂度o(n) 只遍历了一遍height
i, j, res = 0, len(height)-1, 0
# 面积计算公式 min(height[i],height[j]) * (j-i)
while i < j:
if height[i] < height[j]:
res = max(res, height[i] * (j-i))
i += 1
else:
res = max(res, height[j] * (j-i))
j -= 1
return res
5.三数之和
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# 时间复杂度 O(N^2):其中固定指针k循环复杂度 O(N),双指针 i,j 复杂度 O(N)。
# 空间复杂度 O(1):指针使用常数大小的额外空间。
nums.sort()
res, k = [], 0
for k in range(len(nums)-2):
if nums[k] > 0:
break # 最大的都大于0 其余的不可能大于0
if k > 0 and nums[k] == nums[k-1]:
# print(k,nums[k],nums[k-1])
continue # 去重问题
i, j = k+1, len(nums) - 1
while i < j:
s = nums[k] + nums[i] + nums[j]
if s < 0:
i += 1
while i < j and nums[i] == nums[i - 1]:
i += 1 # 防止nums[i]重复
elif s > 0:
j -= 1
while i < j and nums[j + 1] == nums[j]:
j -= 1 # 防止nums[j]重复
else:
res.append([nums[k],nums[i],nums[j]])
i += 1
j -= 1
while i < j and nums[i] == nums[i-1]:
i += 1 # 防止nums[j]重复
while i < j and nums[j+1] == nums[j]:
j -= 1 # 防止nums[j]重复
return res
二刷
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# 时间复杂度 O(N^2):其中固定指针k循环复杂度 O(N),双指针 i,j 复杂度 O(N)。
# 空间复杂度 O(1):指针使用常数大小的额外空间。
nums.sort()
res =[]
# 三数之和 因此只要遍历到倒数第3个位置
for i in range(0,len(nums)-2):
if nums[i] > 0:
break # nums[i]是最小的因此如果大于0,没有可能其他的大于0
if i > 0 and nums[i] == nums[i-1]:
continue # 避免i出现重复
j = i + 1
k = len(nums)-1
while j < k:
s = nums[i] + nums[j] +nums[k]
if s > 0:
k -= 1
while j < k and nums[k] == nums[k+1]: # 只要判断相邻的 而不是在while中写if 否则会超时
k -= 1
elif s < 0:
j += 1
while j < k and nums[j] == nums[j-1]:
j += 1
else:
res.append([nums[i], nums[j], nums[k]])
j += 1
k -= 1
while j < k and nums[k] == nums[k+1]:
k -= 1
while j < k and nums[j] == nums[j-1]:
j += 1
return res
6.接雨水
class Solution(object):
def trap(self, height):
"""
:type height: List[int]
:rtype: int
"""
# 时间复杂度o(n)
# 空间复杂度o(n)
# 每个位置想象为一个桶,而接水的高度 取决于桶两边的高度
# (右边的高度取决于右边所有模板的最大高度
# 左边的高度取决于左边所有模版的最大高度)
# 整个水桶所装的水则 min(桶左右边的高度) - h
n = len(height)
pre_max = [0] * n #前缀最大值
pre_max[0] = height[0]
for i in range(1,n):
pre_max[i] = max(pre_max[i-1], height[i])
suf_max = [0] * n # 后缀最大值
suf_max[-1] = height[-1]
for i in range(n-2, -1, -1):
suf_max[i] = max(suf_max[i+1], height[i])
ans = 0
for h, pre, suf in zip(height, pre_max, suf_max):
ans += min(pre, suf) - h
return ans
7.无重复字符串的最长子串
- 代码链接
- 滑动窗口 + 哈希表
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
# 时间复杂度 o(n)
# 空间复杂度 哈希表最多存储为ASCII码 o(128) = o(1)
if len(s) <= 1:
return len(s) # 注意特殊值
res = 0
dic, i, j = {}, -1, 0
for j,c in enumerate(s):
if c in dic:
i = max(i, dic[c]) # 此时更新左端点只要从上一次出现的位置开始即可
dic[c] = j # 哈希表记录
res = max(res, j - i) # 更新结果为窗口长度,注意第一个j = 0,因此窗口的起始值应该是-1
return res
8.找到字符串中所有的字母异位词
class Solution(object):
def findAnagrams(self, s, p):
"""
:type s: str
:type p: str
:rtype: List[int]
"""
# 时间复杂度 o(m+(n−m)×Σ)
# 空间复杂度 o(Σ)
# 从s中找到所有p的异位词,返回的是所有p子串的起始索引
s_len, p_len = len(s), len(p) # 获取两个字符串的长度
if s_len < p_len:
return [] # 如果s比p短,就不可能存在p的异位词
ans = []
s_count = [0] * 26
p_count = [0] * 26
# 1.维护窗口大小为p的滑动窗口 2.统计p中字符串情况
for i in range(p_len):
s_count[ord(s[i]) - 97] += 1
p_count[ord(p[i]) - 97] += 1 # 计算每个字符出现的次数
if s_count == p_count:
ans.append(0) # 说明s的前p个字符就符合要求了
for i in range(s_len - p_len):
# 模拟窗口滑动
s_count[ord(s[i]) - 97] -= 1
s_count[ord(s[i + p_len]) - 97] += 1
# 注意如果满足的时候 窗口的起始索引为 i + 1
if s_count == p_count:
ans.append(i + 1) #添加索引
return ans
9.和为k的子数组
class Solution(object):
def subarraySum(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
"""
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。
子数组是数组中元素的连续非空序列。
"""
# 思路:想到前缀和 对于数组任何位置j,前缀和 pre[j]指代的是0到j所有元素的总和
# 因此 i+1到j的和 = pre[j] - pre[i]
# k = pre[j] - pre[i],pre[j] = pre[i] + k
ans = 0
# 存储的是 key是前缀和 value是次数
pre_sum = collections.defaultdict(int)
pre_sum[0] = 1 # 和为0出现的次数 空集也是子集
prefixsum = 0
for i in range(len(nums)):
prefixsum += nums[i]
ans += pre_sum[prefixsum - k]
pre_sum[prefixsum] += 1
return ans
10.滑动窗口最大值
class Solution(object):
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
"""
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
"""
# 滑动窗口 考虑双端队列
if not nums or k == 0:
return []
deque = collections.deque()
for i in range(k):
# 形成队列 注意其中这个队列是递减的 然后我们每次要维持队首的数据是最大的
while deque and deque[-1] < nums[i]:
deque.pop()
deque.append(nums[i])
res = [deque[0]] # 别忘了第一个元素
for i in range(k, len(nums)):
# 如果此时要删除的元素刚好是滑动队列中最大的,则需要手动踢出队列pop 否则该元素已经在下一步被pop掉了
if deque[0] == nums[i - k]:
deque.popleft()
# 把所有比当前要加入的元素小的在队列中都剔除,以便于窗口移动
while deque and deque[-1] < nums[i]:
deque.pop()
deque.append(nums[i])
res.append(deque[0])
return res
11.最小覆盖子串
class Solution(object):
def minWindow(self, s, t):
"""
:type s: str
:type t: str
:rtype: str
"""
if len(s) < len(t):
return ""
need = collections.defaultdict(int)
for i in range(len(t)):
need[t[i]] += 1
needCnt = len(t) # 所需要的最少的字母数量
left = 0
res = (0, float("inf")) # 初始化结果
# print(need)
for i in range(len(s)):
if need[s[i]] > 0: # 判断该字母是否有 因为初始化是0
needCnt -= 1 # 如果是所需要的 就数量减1
# 凑齐了字符串
need[s[i]] -= 1
if needCnt == 0:
while True :
#print(i,left,need[s[left]],res)
c = s[left]
#print(c,need[c])
if need[c] == 0: # 如果这个字母是必须的 那么count >= 0
break # 不可缺少这个字母
need[c] += 1
left += 1 # 字符串开头开始向右移动 两步操作 1是need这个hash表变化 2是index移动
# 直到找到符合的最小子串就会break
# 存储结果
if (i - left) < (res[1] - res[0]):
res = (left, i)
# 找到窗口后 寻找下一个窗口
need[s[left]] += 1 # 注意这里要复原的是s[left] left要往内移动
left += 1
needCnt += 1
return "" if res[1] > len(s) else s[res[0] : res[1] + 1]
# # need 作用存储此时窗口中的字母为了达到 target中 所差的个数
# need = collections.defaultdict(int) # 创建一个字典 并且在默认值上为0
# # 构建need 用t 初始化need
# for c in t:
# need[c] += 1
# needCnt = len(t) # 所需要的字母总数量
# i = 0
# res = (0, float("inf")) # 初始化区间的值
# # enumerate 指的迭代器 j是index c是s中的字母
# for j,c in enumerate(s):
# # 判读 c 是否是 t 中必要的元素
# if need[c] > 0:
# needCnt -= 1 # 所需的字母数减1
# need[c] -= 1
# # 1.增加j直到当前窗口中包含了所有必须的字母
# if needCnt == 0:
# while True:
# c = s[i]
# if need[c] == 0: # 2.增加i知道找到t中所需的字母(所需的字母=0 多余<0)
# break
# need[c] += 1
# i += 1
# if j - i < res[1] - res[0]:
# res = (i,j)
# need[s[i]] += 1 # 3.在找到这样的窗口之后 需要增加i找下一个窗口
# i += 1
# needCnt += 1
# # 如果res从未被更新 则是空 否则根据下标获得子串
# return "" if res[1] > len(s) else s[res[0] : res[1]+1]
12.最大子数组和
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
# 动态规划
# dp[i] 包含nums[i]的最大连续字数组合
if len(nums) == 1:
return nums[0]
dp = [0] * len(nums)
dp[0] = nums[0]
for i in range(1, len(nums)):
dp[i] = max(dp[i-1] + nums[i], nums[i]) # 要么包括该nums[i]组成 要么重新开始
print(dp)
return max(dp)
13.合并区间
class Solution(object):
def merge(self, intervals):
"""
:type intervals: List[List[int]]
:rtype: List[List[int]]
"""
# 时间复杂度 o(nlogn)
# 空间复杂度 o(logn) _ 排序需要的区间
intervals.sort(key = lambda x:x[0]) # 以左端点开始排序
res = []
start = intervals[0][0]
end = intervals[0][1]
for i in range(1, len(intervals)):
# print(i,j)
if intervals[i][0] <= end: # 注意比较的是下一个区间头是否在其中
# start = min(start, intervals[i][0])
end = max(end, intervals[i][1])
else:
res.append([start, end])
start = intervals[i][0]
end = intervals[i][1]
res.append([start, end]) # 注意其中最后一次的区间要放进去
return res
14.轮转数组
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: None Do not return anything, modify nums in-place instead.
"""
# 轮转数组k次 时间复杂度 o(n) 空间复杂度 o(1)
# 注意点 1 轮转k 相当于转 k mod n
# 2. 轮转数组相当于 先整体翻转 接着翻转前k个,再反转后面 n-k 个
k = k % len(nums)
self.reverse(nums, 0, len(nums)-1)
self.reverse(nums, 0, k - 1)
self.reverse(nums, k, len(nums)-1)
def reverse(self, s, left, right):
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
15.除自身以外的乘积
class Solution(object):
def productExceptSelf(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
"""
原数组: [1 2 3 4]
左部分的乘积: 1 1 1*2 1*2*3
右部分的乘积: 2*3*4 3*4 4 1
结果: 1*2*3*4 1*3*4 1*2*4 1*2*3*1
"""
# 例【1,2,3,4】
# 注意求的是乘积 因此初始化是1开始的
ans = [1] * len(nums)
for i in range(1, len(nums)): # 【1,1,1*2,1*2*3】
ans[i] = ans[i-1] * nums[i-1] # 计算的是上面行的乘积
print(ans)
tmp = 1
# 右边区域值是从右到左变小的 因此是要倒序
# tmp 应该是 【 1,4,3*4,2*3*4】
for j in range(len(nums) - 2, -1, -1):
tmp *= nums[j+1]
ans[j] *= tmp
return ans