1. 两数之和
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# # 排序 + 双指针
# n = len(nums)
# nums_new = [(nums[i], i) for i in range(n)]
# nums_sort = sorted(nums_new, key=lambda x: x[0])
# # print(nums_new)
# i, j = 0, len(nums)-1
# while i < j:
# s = nums_sort[i][0] + nums_sort[j][0]
# if s == target:
# return [nums_sort[i][1], nums_sort[j][1]]
# elif s < target:
# i += 1
# else:
# j -= 1
# return []
# hash 表
hashtable = dict()
for i, num in enumerate(nums):
if target - num in hashtable:
return [hashtable[target - num], i]
hashtable[nums[i]] = i
return []
3. 无重复字符的最长子串
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
# 滑动窗口
# 一次遍历字符串,判断 s[i] 是否在滑动窗口内,是的话滑动窗口向右缩窄,直到 s[i]不在窗口内
# 更新窗口长度和最长子串长度
n = len(s)
left = 0
str_set = set() # 窗口内的字符集合
cursize = 0
maxsize = 0
for i in range(n):
cursize += 1
# 缩小窗口,直到窗口内不重复
while s[i] in str_set:
str_set.remove(s[left])
cursize -= 1
left += 1
str_set.add(s[i])
# 更新 maxsize
maxsize = max(maxsize, cursize)
return maxsize
4. 寻找两个正序数组的中位数
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
# # 合并两个排序数组,时间复杂度 O(m+n)
# ans = []
# m, n = len(nums1), len(nums2)
# i, j = 0, 0
# while i < m or j < n:
# if i == m:
# ans.append(nums2[j])
# j += 1
# elif j == n:
# ans.append(nums1[i])
# i += 1
# elif nums1[i] <= nums2[j]:
# ans.append(nums1[i])
# i += 1
# else:
# ans.append(nums2[j])
# j += 1
# if (m + n) % 2 == 0:
# return (ans[(m+n) // 2 - 1] + ans[(m+n) // 2]) / 2
# else:
# return ans[(m+n) // 2]
# 查找第 k 个小的元素
"""
- 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
- 这里的 "/" 表示整除
- nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
- nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
- 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
- 这样 pivot 本身最大也只能是第 k-1 小的元素
- 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
- 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
- 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
"""
def getKElement(k):
index1, index2 = 0, 0
# m, n = len(nums1), len(nums2)
while True:
# 截止条件
if index1 == m:
return nums2[index2 + k - 1]
if index2 == n:
return nums1[index1 + k - 1]
if k == 1:
return min(nums1[index1], nums2[index2])
# 正常判断
newIndex1 = min(index1 + k // 2 - 1, m - 1)
newIndex2 = min(index2 + k // 2 - 1, n - 1)
pivot1, pivot2 = nums1[newIndex1], nums2[newIndex2]
if pivot1 <= pivot2:
# 更新 index1 和 k,先更新 k
k -= newIndex1 - index1 + 1
index1 = newIndex1 + 1
else:
# 更新 index2 和 k,先更新 k
k -= newIndex2 - index2 + 1
index2 = newIndex2 + 1
m, n = len(nums1), len(nums2)
if (m+n) % 2 == 1:
return getKElement((m+n)//2+1)
else:
return (getKElement((m+n)//2) + getKElement((m+n)//2+1)) / 2
10. 正则表达式匹配
class Solution:
def isMatch(self, s: str, p: str) -> bool:
s_len = len(s)
p_len = len(p)
# dp[i][j] 表示 s[:i] 与 p[:j] 是否匹配,各自前 i、j 个是否匹配
dp = [[False] * (p_len + 1) for _ in range(s_len + 1)]
dp[0][0] = True
# s 为空串,更新第一行
for j in range(1, p_len + 1):
# 若 p 的第 j 个字符 p[j - 1] 是 '*'
# 说明第 j - 1、j 个可有可无
# 那么如果前 j - 2 个已经匹配上,前 j 个也可以匹配上
if p[j - 1] == '*':
dp[0][j] = dp[0][j - 2]
for i in range(1, s_len + 1):
for j in range(1, p_len + 1):
# 当前字符可以匹配,转移到dp[i-1][j-1]
if p[j - 1] in {s[i - 1], '.'}:
dp[i][j] = dp[i - 1][j - 1]
# 当前字符是*,需要和 p 前一个字符一起判断
elif p[j - 1] == '*':
# 如果 p 前一个字符可以匹配 s 最后一个字符,可以选择该[*组合]不匹配 或者 去掉 s 最后一个字符继续匹配
if p[j - 2] in {s[i - 1], '.'}:
dp[i][j] = dp[i][j - 2] or dp[i - 1][j]
# 如果 p 前一个字符不能匹配 s 最后一个字符,只能选择该 [*组合] 不匹配
else:
dp[i][j] = dp[i][j - 2]
return dp[s_len][p_len]
11. 盛最多水的容器
class Solution:
def maxArea(self, height: List[int]) -> int:
# 双指针,每次移动短板,更新最大值
# 移动短板,新的面积可能增大;移动长板,面积一定减小
i, j = 0, len(height)-1
res = 0
while i < j:
res = max(res, (j-i)*min(height[i], height[j]))
if height[i] <= height[j]:
i += 1
else:
j -= 1
return res
20. 有效的括号
class Solution:
def isValid(self, s: str) -> bool:
# 栈,左括号入栈,遇到右括号出栈,判断是否匹配
stack = []
for i in range(len(s)):
if s[i] in {'(', '[', '{'}:
stack.append(s[i])
else:
if len(stack) == 0:
return False
ch = stack.pop()
if s[i] == ')' and ch == '(':
continue
if s[i] == ']' and ch == '[':
continue
if s[i] == '}' and ch == '{':
continue
return False
if len(stack) == 0:
return True
return False
31. 下一个排列
![](https://img-blog.csdnimg.cn/9ee61e6f89ef4350944e1e695940f2fc.png)
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 两遍搜索,第一遍从后向前查找【小数】(第一个非升序的数),第二遍从后向前查找【大数】(大于小数的第一个数)
# 交换【小数】和【大数】
# 【大数】后的序列升序排列(双指针)
n = len(nums)
i = n-2
# 找【小数】
while i >= 0 and nums[i] >= nums[i+1]:
i -= 1
# 找【大数】(如果原序列为逆序排列,没有小数,直接返回倒序序列)
if i >= 0:
j = n-1
while j > i and nums[j] <= nums[i]:
j -= 1
# print(i, j)
# 交换
nums[i], nums[j] = nums[j], nums[i]
# print(nums)
# 逆序 大数之后的序列
i, j = i+1, n-1
while i < j:
nums[i], nums[j] = nums[j], nums[i]
i += 1
j -= 1
return
48. 旋转图像
力扣
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
# 找规律
n = len(matrix)
for i in range(n // 2):
for j in range((n + 1) // 2):
matrix[i][j], matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1] \
= matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1], matrix[i][j]
# 水平翻转 + 对角线翻转
n = len(matrix)
# 水平翻转
for i in range(n // 2):
for j in range(n):
matrix[i][j], matrix[n - i - 1][j] = matrix[n - i - 1][j], matrix[i][j]
# 主对角线翻转
for i in range(n):
for j in range(i):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
72. 编辑距离
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
# 动态规划
# dp[i][j]表示 word1[:i+1]转换到word2[:j+1]所需要的最少操作数
# 如果 word1[i] = word2[j], dp[i][j] = dp[i-1][j-1];
# 否则,dp[i][j] = min(dp[i-1][j-1](替换), dp[i][j-1](插入), dp[i-1][j](删除)) + 1
# 第一行:表示 word1 为空,插入操作
# 第一列:表示 word2 为空,删除操作
m = len(word1)
n = len(word2)
dp = [[0]* (n+1) for _ in range(m+1)]
# 单独处理第一行和第一列
for j in range(n+1):
dp[0][j] = j
for i in range(m+1):
dp[i][0] = i
for i in range(1, m+1):
for j in range(1, n+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j-1], dp[i][j-1], dp[i-1][j]) + 1
return dp[m][n]
76. 最小覆盖子串
class Solution:
def minWindow(self, s: str, t: str) -> str:
'''
如果hs哈希表中包含ht哈希表中的所有字符,并且对应的个数都不小于ht哈希表中各个字符的个数,那么说明当前的窗口是可行的,可行中的长度最短的滑动窗口就是答案。
'''
if len(s) < len(t):
return ""
hs, ht = defaultdict(int), defaultdict(int)#初始化新加入key的value为0
# 初始化 ht
for char in t:
ht[char] += 1
res = ""
left, right = 0, 0 # 滑动窗口
cnt = 0 # 当前窗口中满足ht的字符个数
while right < len(s):
hs[s[right]] += 1
# 更新 cnt,之后用来判断是否当前窗口满足条件(包含 t 中所有字符)
if hs[s[right]] <= ht[s[right]]:
cnt += 1
# 判断是否可以缩窄窗口
while left <= right and hs[s[left]] > ht[s[left]]:
hs[s[left]] -= 1
left += 1
# cnt 满足条件,更新 res
if cnt == len(t):
if not res or right-left+1 < len(res): #res为空或者遇到了更短的长度
res = s[left:right+1]
right += 1
return res
84. 柱状图中最大的矩形
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
# # 暴力解法
# size = len(heights)
# res = 0
# for i in range(size):
# left = i
# cur_height = heights[i]
# while left > 0 and heights[left - 1] >= cur_height:
# left -= 1
# right = i
# while right < size - 1 and heights[right + 1] >= cur_height:
# right += 1
# max_width = right - left + 1
# res = max(res, max_width * cur_height)
# return res
# 单调栈
stack = []
heights = [0] + heights + [0]
res = 0
for i in range(len(heights)):
#print(stack)
# 当遇到一个小的,说明找到了右边界
# 由于是单调递增栈,左边界就是栈顶元素
while stack and heights[i] < heights[stack[-1]]:
tmp = stack.pop()
res = max(res, (i - stack[-1] - 1) * heights[tmp])
stack.append(i)
return res
85. 最大矩形
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
# 暴力求解,时间复杂度 O(m^2*n)
# 二维数组 dp[i][j],存储第 i 行第 j 个数,向左连续的 1 的个数
# 遍历 dp,找到以matrix[i][j为右下角端点的最大矩形面积(向上找最小宽度 * 高度)
m = len(matrix)
n = len(matrix[0])
dp = [[0]*n for _ in range(m)]
# 构建 dp 数组
for i in range(m):
for j in range(n):
if matrix[i][j] == '0':
dp[i][j] = 0
else:
dp[i][j] = (dp[i][j-1] + 1) if j > 0 else 1
# print(dp)
# # 遍历右下角端点,计算最大面积
# max_area = 0
# for i in range(m):
# for j in range(n):
# if dp[i][j] == 0:
# continue
# min_width = dp[i][j]
# # 从当前行,向上寻找最小的宽度,同时计算高度,计算最大面积
# for k in range(i, -1, -1):
# min_width = min(min_width, dp[k][j])
# max_area = max(max_area, min_width * (i-k+1))
# return max_area
# 单调栈,和 84 题一样
# 把 dp 的每一列,作为高度数组传入,可计算出每一列的最大面积,最后取 max
def largestRectangleArea(heights: List[int]) -> int:
# 单调栈
stack = []
heights = [0] + heights + [0]
res = 0
for i in range(len(heights)):
#print(stack)
# 当遇到一个小的,说明找到了右边界
# 由于是单调递增栈,左边界就是栈顶元素
while stack and heights[i] < heights[stack[-1]]:
tmp = stack.pop()
res = max(res, (i - stack[-1] - 1) * heights[tmp])
stack.append(i)
return res
max_area = 0
for j in range(n):
heights = [x[j] for x in dp]
max_area = max(max_area, largestRectangleArea(heights))
return max_area
105. 从前序与中序遍历序列构造二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# # 递归,增加索引 map 减小 index 的复杂度
# # 边界条件
# if len(preorder) == 0:
# return None
# if len(preorder) == 1:
# return TreeNode(preorder[0])
# # 构造根节点
# val = preorder[0]
# index = inorder.index(val)
# # 计算左右子树的大小,便于计算左右子树的preorder 和 inorder
# left_size, right_size = index, len(inorder)-1-index
# root = TreeNode(val)
# # 分治,递归构造左右子树
# root.left = self.buildTree(preorder[1:1+left_size], inorder[:index])
# root.right = self.buildTree(preorder[1+left_size:], inorder[index+1:])
# return root
# 迭代
# 用一个栈保存已经遍历过的节点
# 遍历前序遍历的数组,一直作为当前根节点的左子树,直到当前节点和中序遍历的数组的节点相等
# 然后正序遍历中序遍历的数组,倒着遍历已经遍历过的根节点(用栈的 pop 实现)
# 找到最后一次相等的位置,把它作为该节点的右子树。
if not preorder:
return None
root = TreeNode(preorder[0])
stack = [root] # 保存已遍历的节点
inorderIndex = 0
# 正序遍历【前序遍历】数组
for i in range(1, len(preorder)):
preorderVal = preorder[i]
node = stack[-1]
# 和中序遍历节点不相等,就一直作为左子树
if node.val != inorder[inorderIndex]:
node.left = TreeNode(preorderVal)
stack.append(node.left)
else:
# 和中序遍历节点相等,倒着遍历已经遍历过的根节点
while stack and stack[-1].val == inorder[inorderIndex]:
node = stack.pop()
inorderIndex += 1
# 找到最后一次相等的位置,把它作为该节点的右子树
node.right = TreeNode(preorderVal)
stack.append(node.right)
return root
128. 最长连续序列
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
# res = 0
# nums_set = set(nums) # 去重
# for num in nums:
# length = 0
# # 不是最左端点,跳过
# if num-1 in nums_set:
# continue
# # 是最左端点,连续查找数字是否存在,更新长度
# cur_num = num
# while cur_num in nums_set:
# length += 1
# cur_num += 1
# # 更新最大长度
# res = max(res, length)
# return res
# 动态规划
hash_dict = dict()
max_length = 0
for num in nums:
if num not in hash_dict:
left = hash_dict.get(num - 1, 0)
right = hash_dict.get(num + 1, 0)
cur_length = 1 + left + right
if cur_length > max_length:
max_length = cur_length
hash_dict[num] = cur_length
hash_dict[num - left] = cur_length
hash_dict[num + right] = cur_length
return max_length
136. 只出现一次的数字
class Solution:
def singleNumber(self, nums: List[int]) -> int:
# 异或运算
res = 0
for num in nums:
res ^= num
return res
139. 单词拆分
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
# 回溯
if len(s) == 0:
return False
def match(s1, s2):
for i in range(len(s1)):
if s1[i] != s2[i]:
return False
return True
import functools
@functools.lru_cache(None)
def dfs(s):
if len(s) == 0:
return True
for word in wordDict:
if len(s) < len(word):
continue
if not match(word, s[:len(word)]):
continue
# 前几个字符匹配,递归判断剩余字符串
if dfs(s[len(word):]):
return True
return False
return dfs(s)
# # 动态规划
# n = len(s)
# dp = [False] * (n+1)
# dp[0] = True
# for i in range(n+1):
# for j in range(i):
# if dp[j] and s[j:i] in wordDict:
# dp[i] = True
# break
# return dp[n]
# # 使用记忆化函数,保存出现过的 backtrack(s)backtrack(s),避免重复计算。
# import functools
# @functools.lru_cache(None)
# def back_track(s):
# if(not s):
# return True
# for i in range(1,len(s)+1):
# if(s[:i] in wordDict):
# if back_track(s[i:]):
# return True
# return False
# return back_track(s)
198. 打家劫舍
class Solution:
def rob(self, nums: List[int]) -> int:
# 动态规划
# # 二维
# # dp[i][0]表示截止到第 i 间,不偷,获得的最高金额
# # dp[i][1]表示截止到第 i 间,偷,获得的最高金额
# # 状态转移:dp[i][0] = max(dp[i-1][0], dp[i-1][1]); dp[i][1] = dp[i-1][0] + nums[i]
# n = len(nums)
# dp = [[0] * 2 for _ in range(n)]
# dp[0][0] = 0
# dp[0][1] = nums[0]
# for i in range(1, n):
# dp[i][0] = max(dp[i-1][0], dp[i-1][1])
# dp[i][1] = dp[i-1][0] + nums[i]
# print(dp)
# return max(dp[n-1][0], dp[n-1][1])
# # 一维
# # dp[i] 表示截止到第 i 间所获取的最大金额
# # 状态转移:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
# n = len(nums)
# dp = [0 for _ in range(n)]
# dp[0] = nums[0]
# for i in range(1, n):
# dp[i] = max(dp[i-1], (dp[i-2] if i >= 2 else 0) + nums[i])
# return dp[n-1]
# 一维 空间优化
# dp[i] 表示截止到第 i 间所获取的最大金额
# 状态转移:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
n = len(nums)
if n == 1:
return nums[0]
dp_2 = nums[0]
dp_1 = max(nums[0], nums[1])
dp = dp_1
for i in range(2, n):
dp = max(dp_1, dp_2 + nums[i])
dp_2 = dp_1
dp_1 = dp
return dp