03/05/2024
哈希
1. 两数之和
题目描述
解法
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
num_dic = {} #字典哈希来记录每隔元素的下标,key是元素的值,value是元素的下标
for i in range(len(nums)): #遍历元素
if target - nums[i] in num_dic: #如果target - 当前元素的值在num_dic中,也就是在key中
return[i, num_dic[target - nums[i]]] #返回当前元素的下标i和target - 当前元素的下标也就是num_dic对应的value
num_dic[nums[i]] = i #把num_dic的元素赋值为下标
2. 字母异位词分组
题目描述
解法
方法一 暴力
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
hash_map = [[0] * 26 for _ in range(len(strs))]
result = []
for i in range(len(strs)):
for j in strs[i]:
hash_map[i][ord(j) - ord('a')] += 1 #二维哈希map记录每个单词的哈希映射表
for i in range(len(hash_map)): #遍历哈希表,哈希序列相同的元素放在subset里,需要n^2的复杂度,并且还要保证用过的单词不被使用
subset = []
if hash_map[i] != -1:
subset.append(strs[i])
for j in range(i + 1, len(hash_map)):
if hash_map[j] == hash_map[i] and hash_map[j] != -1:
subset.append(strs[j])
hash_map[j] = -1
if subset != []:
result.append(subset.copy())
return result
方法二
:利用字母异位词排序之后是相同的这一特点构建哈希字典,键为排序后的单词组成的字符串
找数组或者列表中的相同元素,可以考虑用相同的属性作为dict的key,利用哈希分组。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
word_dic = {}
for word in strs:
key = ''.join(sorted(word)) #用排序后的单词组成的字符作为key,因为异位词排序之后是一样的
if key in word_dic: #如果当前词在key中,那么key对应的value也就是word的list中加入新的word
word_dic[key].append(word)
else: #如果当前词不在,创建当前的key对应的word的list
word_dic[key] = [word]
return list(word_dic.values()) #最后返回value的list
3. 最长连续序列
题目描述
解法
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
# 利用python set的特性,因为是hashtable形式存储,查找复杂度为O(1)
nums_set = set(nums)
result = 0
for num in nums_set:
# 如果num-1不在nums_set中才说明是起点
if num - 1 not in nums_set:
#cur记录当前的连续长度
cur = 1
#循环判断num的下一个在不在set中,在的话就长度加一,并num也加一继续判断
while num + 1 in nums_set:
num += 1
cur += 1
#result记录最长的长度
result = max(result, cur)
return result
03/06/2024
双指针
4. 移动零
题目描述
解法
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
#双指针
i = 0
j = 0
#右指针一直走,所以循环主体右指针,O(n)复杂度
while(j < len(nums)):
#当不遇到0的时候,做交换,把0换到右边
if nums[j] != 0:
nums[i], nums[j] = nums[j], nums[i]
#此时左指针才移动(操作完才移动),否则不移动
i += 1
j += 1
return nums
5. 盛水最多的容器
题目描述
解法
解法一
暴力解法思想简单,遍历所有的可能组合,找面积最大的,不出意外的超时了,考虑用双指针降低时间复杂度
#暴力求解
class Solution:
def maxArea(self, height: List[int]) -> int:
_max = 0 #记录最大面积
_cur = 0 #记录当前遍历的面积
for i in range(len(height)):
for j in range(len(height)):
cur = min(height[i], height[j]) * (j - i) #当前面积为高度短的边乘以宽度
if cur > _max:
_max = cur
return _max
解法二
双指针解法:
class Solution:
def maxArea(self, height: List[int]) -> int:
# 问题的先验信息在于 面积最大肯定是从两边往中间缩小,所以考虑左指针逐渐增加,右指针逐渐减小
left = 0
right = len(height) - 1
_cur = 0 #记录当前面积
_max = 0 #记录最大面积
while(left <= right):
_cur = (right - left) * min(height[left], height[right]) #面积计算
if _cur >= _max: #记录最大面积
_max = _cur
if (height[left] <= height[right]): #如果左边高度低,继续往右边移动,寻找更高的
left += 1
else: #右边高度低就往左边移动,寻找更高的
right -= 1
return _max
6. 三数之和
题目描述
解法
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
#三数之和实际上就是用外层循环固定第一个数,变成两数之和
nums.sort()
result = []
for i in range(len(nums)):
left = i + 1 #定义左右双指针
right = len(nums) - 1
if i > 0 and nums[i] == nums[i - 1]: #去重,如果第一个数碰到一样的值,直接跳过
continue
while(left < right): #双指针左右往中间移动
if nums[i] + nums[left] + nums[right] > 0: #值大了,右指针左移
right -= 1
elif nums[i] + nums[left] + nums[right] < 0: #值小了,左指针右移
left += 1
else: #相等的情况先去重
while(left < right and nums[left] == nums[left + 1]): #左值和后面的值相等
left += 1
while(left < right and nums[right] == nums[right - 1]): #右值和前面的值相等
right -= 1
result.append([nums[i], nums[left], nums[right]]) #记录结果
left += 1 #两个指针往中间移动
right -= 1
return result
03/08/2024
7. 接雨水
题目描述
解法
超时了,有几个例子没有AC,等后面有时间再补上
class Solution:
def trap(self, height: List[int]) -> int:
result = 0
_max = max(height) + 1
for i in range(1, _max): # 分层去遍历
left_max = 0 # 记录当前层左侧的最高高度
temp = 0 # 蓄水器
for j in range(len(height)): # 每个高度都遍历一遍找当前高度加了多少水
if height[j] >= i: # 如果遍历到的高度大于当前层高
left_max = i # 更新左侧最高高度为当前层高
result += temp # 结果加上蓄水器中的水,因为碰到更高的就可以把蓄水器的水加到结果中
temp = 0 # 重置蓄水器
if height[j] < left_max:
temp += 1 # 如果遍历到的高度小于当前层高,蓄一格水,直到后面碰到更高的柱子就把蓄水器中水加到结果中,否则就一直存在蓄水器中
return result
滑动窗口
8. 无重复字符的最长子串
题目描述
解法
解法一
解法1:两层循环遍历,左边界的循环导致复杂度比较高,可以用字典记录每个字符前一次出现的位置来降低复杂度
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if len(s) == 0: #考虑两个边界情况
return 0
if len(s) == 1:
return 1
left = 0
right = 0
result = 0
cur = 0
while(right < len(s) - 1): #双指针,右指针一直移动
while s[right + 1] in list(s[left:right + 1]): #如果下一个字符已经在区间里面了
left += 1 #循环移动左边界,直到下一个字符不在里面
right += 1 #右边界向右移动一格
cur = right - left + 1 #记录当前的长度
result = max(result, cur)
return result
解法二
: 用一个字典记录每个字符所在的位置,每次left就取差找到的位置+1和自己的相比更大的那个
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
left = 0
right = 0
cur = 0
result = 0
left_index = {}
while right < len(s):
if s[right] in left_index:
left = max(left, left_index[s[right]] + 1)
left_index[s[right]] = right
cur = right - left + 1
result = max(result,cur)
right += 1
return result
9. 找到字符串中所有字母异位词
题目描述
解法
解法一
解法1 暴力求解:直接遍历一遍,比较所有的s的子串sorted之后和p sorted之后是否相等,但是超市了
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
result = []
gap = len(p)
for i in range(len(s) - gap + 1):
if sorted(s[i: i + gap]) == sorted(p):
result.append(i)
return result
复杂度在于每次都需要排序后比较,实际上可以通过hash数组来比较,每次遍历的时候,数组对出去的字符和进来的字符进行一次更新就好了
解法二
解法2 hash + 滑动窗口的思想
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
m, n, res = len(s), len(p), [] # m,n表示串s和串p的长度
if m < n: # 如果m < n直接返回空
return res
s_map = [0] * 26 # 定义两个hash数组记录两个串的hash值
p_map = [0] * 26
for i in range(n):
s_map[ord(s[i]) - ord('a')] += 1
p_map[ord(p[i]) - ord('a')] += 1
if s_map == p_map: # 如果一开始就相等,加入索引0
res.append(0)
for i in range(n, m): # 相当于窗口遍历一遍,每次去掉最左边的字母的hash值和加入最右边字母的hash值,然后比较是否相等
s_map[ord(s[i - n]) - ord('a')] -= 1
s_map[ord(s[i]) - ord('a')] += 1
if s_map == p_map:
res.append(i - n + 1)
return res
子串
10. 和为k的子数组
11. 滑动窗口最大值
12. 最小覆盖子串
普通数组
13. 最大子数组和
题目描述
解法
解法一
贪心,要注意result的初始化为无穷小,这样就可以从第一个索引开始判断
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
result = float('-inf')
cur = 0
for i in range(len(nums)):
cur += nums[i]
result = max(cur, result)
if cur < 0:
cur = 0
return result
解法二
动态规划
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
dp = [0] * len(nums) #以i结尾连续子数组的最大和
dp[0] = nums[0]
#dp[i] = max(nums[i], dp[i - 1] + nums[i])
for i in range(1, len(nums)):
dp[i] = max(nums[i], dp[i - 1] + nums[i])
return max(dp)
14. 合并区间
题目描述
解法
解法一:
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key = lambda x: (x[0], x[1])) #按区间头排序,区间头相同则按区间尾排
result = [] #记录结果
if len(intervals) == 1: #如果区间只有一个,那么直接返回
return intervals
for i in range(len(intervals) - 1):
if intervals[i + 1][0] > intervals[i][1]: #区间不重合的情况,就把前一个区间加入
result.append(intervals[i])
if i == len(intervals) - 2: #如果遍历到最后一个也不重合,那就把最后一个区间也加入
result.append(intervals[i + 1])
else: #区间重合了
intervals[i + 1][0] = intervals[i][0] #把下一个区间的头变成两个区间最小的头,尾变成最大的尾,也就是合并
intervals[i + 1][1] = max(intervals[i][1], intervals[i + 1][1])
if i == len(intervals) - 2: #如果遍历到最后一个了,就把合并完的区间加入
result.append(intervals[i + 1])
return result
解法二:
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key = lambda x : x[0])
merged = [] #定义一个结果栈,把每个区间看成入栈的元素
for i in intervals:
if not merged or merged[-1][1] < i[0]: #如果栈为空或者栈顶元素的右区间小于当前元素的左区间,满足入栈条件,新元素入栈
merged.append(i)
else: #不满足条件的话,把栈顶元素的右区间更新为两者更大的那个
merged[-1][1] = max(i[1], merged[-1][1])
return merged
15. 轮转数组
题目描述
解法
解法1
三次反转,之前字符串换位置也用到了
def reverse(nums, left, right):
i, j = left, right
while(i <= j):
nums[i], nums[j] = nums[j], nums[i]
i += 1
j -= 1
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
k = k % len(nums) #有效反转步数
reverse(nums, 0, len(nums) - k - 1) #反转倒数k个前面的
reverse(nums, len(nums) - k, len(nums) - 1) #反转倒数k个
reverse(nums, 0 , len(nums) - 1) #整个反转
解法2
直接用python的切片交换位置
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
if step := k % len(nums): #计算表达式并赋值,如果是0的话就不做操作,对应的就是0步=不动
nums[:step], nums[step:] = nums[-step:], nums[:-step]
16. 除自身以外数组的乘积
题目描述
解法
不能使用除法,结果等于左边元素的乘积再乘以右边元素的乘积,第一次循环计算每个元素左边元素的乘积并保存,第二次循环记录每个元素右边元素的乘积,并乘到第一次记录的结果中作为最后的结果
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
#不能使用除法,即每个数除自身外的乘积为左边的值乘积乘以右边的值乘积
ans = [1] * len(nums)
tmp = 1
for i in range(1, len(nums)):
ans[i] = ans[i - 1] * nums[i - 1]
for i in range(len(nums) - 2, -1, -1):
tmp *= nums[i + 1]
ans[i] *= tmp
return ans
17. 缺失的第一个正数
题目描述
解法
哈希 set O(1)复杂度
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
nums = set(nums) #转化为hash set 查找复杂度O(1)
i = 1
import sys
while(i < sys.maxsize):
if i in nums:
i += 1
else:
return i
矩阵
18. 矩阵置零
19. 螺旋矩阵
20. 旋转图像
21. 搜索二维矩阵Ⅱ
链表
22. 相交链表
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
visited_A = set() #集合记录A链表中所有节点
while(headA):
visited_A.add(headA)
headA = headA.next
while(headB): #遍历B,第一个在A中的节点就是入口
if headB in visited_A:
return headB
headB = headB.next
return None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
# 处理边缘情况
if not headA or not headB:
return None
# 在每个链表的头部初始化两个指针
pointerA = headA
pointerB = headB
# 遍历两个链表直到指针相交
while pointerA != pointerB:
# 将指针向前移动一个节点
pointerA = pointerA.next if pointerA else headB
pointerB = pointerB.next if pointerB else headA
# 如果相交,指针将位于交点节点,如果没有交点,值为None
return pointerA
# 大神的解法,核心在于让两个指针都走A+B的长度,或者是说相交前的长度
23. 反转链表
题目描述
解法
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
left = None #左指针指向空
right = head #右指针指向头节点
while(right): #右指针不到最后一个
tmp = right.next #记录右指针下一个节点的位置
right.next = left #右指针的节点的next为左指针指向的位置
left = right #移动左右指针
right = tmp
return left
24. 回文链表
25. 环形链表
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
visited = set() #集合记录每个visited过的节点
while(head):
if head in visited: #如果已经纪录过就retrun True
return True
visited.add(head) #遍历把节点加入到集合中
head = head.next
return None
26. 环形链表Ⅱ
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow = head #慢指针一次移动一个节点
fast = head #快指针一次移动两个节点
while(fast and fast.next): #当快指针不为空或者下一步不为空
slow = slow.next #移动两个指针
fast = fast.next.next
if slow == fast: #如果相遇了,此时从相遇节点出发一个节点
slow = head #也从头节点出发一个节点
while(slow != fast): #再次相遇的时候,头节点出发的节点就会到环入口处
slow = slow.next
fast = fast.next
return slow
return None
# x:头节点到环入口节点的距离
# y:环入口节点到第一次相遇节点的距离
# z:环内剩下节点
# x+y: 慢指针到相遇时走过的节点个数
# x+y+n(y+z): 快指针到相遇时走过的节点个数,n为多走的环圈数
# 2(x+y) = x+y + n(y+z) :慢指针因为一次走一个,所以需要两倍
# x=(n-1)(y+z) + z
# if n==1: x=z 即代码的结论
27. 合并两个有序链表
28. 两数相加
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
cur = Dummy = ListNode(-1) #定义新的头节点
carry = 0 #记录每次的和
while(l1 or l2 or carry): #如果有进位或者还有没加完的值
carry += (l1.val if l1 else 0) + (l2.val if l2 else 0) #carry记录当前和
cur.next = ListNode(carry % 10) #定义下一个节点为carry % 10
carry //=10 #还余下的值为进位,到下一轮求和
cur = cur.next #顺序推进一个个进行
if l1: l1 = l1.next
if l2: l2 = l2.next
return Dummy.next
29. 删除链表的倒数第N个结点
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
DummyHead = ListNode(next = head) #定义虚拟头节点
left = DummyHead #快慢指针
right = head
for _ in range(n): #让快指针先走n步
right = right.next
while(right): #此时快慢指针同时走
right = right.next
left = left.next
left.next = left.next.next #快指针到最后的时候 慢指针就指向要删除节点的前一个
return DummyHead.next
30. 两两交换链表中的节点
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummyHead = ListNode(0) #虚拟头节点
dummyHead.next = head
temp = dummyHead #指针指向虚拟头节点
while temp.next and temp.next.next: #当下一个和下下个都存在
node1 = temp.next #定义需要交换的两个节点,避免混淆
node2 = temp.next.next
temp.next = node2 #temp指向第二个节点
node1.next = node2.next #第一个节点的下一个指向第二个节点的下一个
node2.next = node1 #第二个节点的下一个指向节点一
temp = node1 #temp指向接下来要交换的两个节点的前一个,即node1
return dummyHead.next
31. K个一组翻转链表
32. 随机链表的复制
33. 排序链表
34. 合并K个升序链表
35. LRU缓存
二叉树
36. 二叉树中序遍历
递归法:
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
def Traversal(result, cur):
if cur == None:
return
Traversal(result, cur.left)
result.append(cur.val)
Traversal(result, cur.right)
Traversal(result, root)
return result
37. 二叉树最大深度
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
def traversal(root):
if not root:
return 0
left_length = traversal(root.left)
right_length = traversal(root.right)
return max(left_length, right_length) + 1 #抽象为最小子问题的话,深度就是左右子树的深度最大的+1
return traversal(root)
38. 翻转二叉树
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
def invert(root): #深搜
if not root:
return #空节点返回
tmp = root.left #针对递归的最小子问题实现翻转
root.left = root.right
root.right = tmp
invert(root.left) #递归函数分别实现左右子树
invert(root.right)
invert(root)
return(root)
39. 对称二叉树
class Solution:
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
def dfs(root1, root2): #分别深搜左右节点,返回是否对称
if not root1 and not root2: #左右都为空 返回True
return True
elif not root2 and root1: #左右不相等 返回False
return False
elif not root1 and root2:
return False
elif root1.val != root2.val:
return False
flag1 = dfs(root1.left, root2.right) #深搜左子树的左节点和右子树的右节点
flag2 = dfs(root1.right, root2.left) #左子树的右节点和右子树的左节点
return (flag1 and flag2) #两个都对称的话返回true
return dfs(root.left, root.right)
40.二叉树直径
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
def dfs(root, result): #返回两个左树和右树深度和, result 记录最大值
if not root:
return -1
left_length = dfs(root.left, result) + 1
right_length = dfs(root.right, result) + 1
max_length = left_length + right_length
result.append(max_length)
return max(left_length, right_length)
result = []
dfs(root, result)
return max(result)
41. 二叉树层序遍历
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
result = []
if not root:
return result
from collections import deque
q = deque()
q.append(root)
while q:
subset = []
for _ in range(len(q)):
cur = q.popleft()
if cur.left:
q.append(cur.left)
if cur.right:
q.append(cur.right)
subset.append(cur.val)
result.append(subset)
return result
42. 平衡二叉树![](https://img-blog.csdnimg.cn/direct/7cf006bb2e0f4c6988f678c28e1b9dc5.png)
class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
def dfs(root):
if not root:
return 0
left_height = dfs(root.left)
right_height = dfs(root.right)
if left_height == -1 or right_height == -1 or left_height - right_height > 1 or right_height - left_height > 1:
return -1
else:
return max(left_height, right_height) + 1
return dfs(root) >= 0
回溯
55. 全排列
56. 子集
题目描述
解法
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
def back_tracking(result, subset, nums, start_index):
result.append(subset.copy())
for i in range(start_index, len(nums)):
subset.append(nums[i])
back_tracking(result, subset, nums, i + 1)
subset.pop()
result = []
subset = []
start_index = 0
back_tracking(result, subset, nums, start_index)
return result
57. 电话号码的字母组合
58. 组合总和
题目描述
解法
标准的回溯算法解法,不能重复使用,所以start_index为i + 1
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort() #排序,从小到大
def back_tracking(candidates, target, result, subset, start_index): #组合问题 回溯
if sum(subset) == target: #子集和为target, 记录结果
result.append(subset.copy())
return
if sum(subset) > target: #和大于target, 直接返回
return
for i in range(start_index, len(candidates)): #横向遍历,每次往subset中加一个candidate
subset.append(candidates[i])
back_tracking(candidates, target, result, subset, i) #因为可以重复选用, start_index就从i开始,不能的话就i+1
subset.pop() #回溯返回之后回退一个
result = []
subset = []
back_tracking(candidates, target, result, subset, 0)
return result
59. 括号生成
60. 单词搜索
61. 分割回文串
62. N 皇后
二分查找
63. 搜索插入位置
64. 搜索二维矩阵
65. 排序数组中查第一个和最后一个位置
66. 搜索旋转排序数组
67. 旋转排序数组最小值
68. 两个正序数组中位数
栈
69. 有效的括号
70. 最小栈
71. 字符串解码
72. 每日温度
73. 柱状图中最大的矩形
堆
74. 数组中的第K个最大元素
75. 前K个高频元素
题目描述
解法
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
hash_ = {}
for i in range(len(nums)): #字典统计每个词出现的频率
if nums[i] not in hash_:
hash_[nums[i]] = 1
else:
hash_[nums[i]] += 1
hash_sort = dict(sorted(hash_.items(), key = lambda x: -x[1])) #对字典按value排序,即按出现的频率排序,注意sorted函数返回值是list,可转化回dict
result = []
for i, val in enumerate(hash_sort.keys()): #打印前k个key,就是前k个高频元素
if i >= k:
break
result.append(val)
return result
76. 数据流的中位数
贪心算法
77. 买卖股票的最佳时机
题目描述
解法
解法一
贪心算法:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
max_profit = 0 #记录最大收益
cur_profit = 0 #记录当前累计的收益
for i in range(1, len(prices)):
day_profit = prices[i] - prices[i - 1] #每日收益
cur_profit += day_profit #每日收益累加
if cur_profit < 0: #如果收益小于0, 直接置零,含义是之前不买卖
cur_profit = 0
max_profit = max(max_profit, cur_profit)#记录最大的累积收益,和连续最大子数组和比较类似,数组的元素就是每日的profit
return max_profit
解法二
动态规划:dp数组记录两个状态,即每天持有和不持有的手里最大现金
每天持有的手里最大现金应该是昨天就持有dp[i-1][1]和当天买入0-nums[i]
因为只能买卖一次,所以当天买入前的手里现金就是0
如果买卖多次的话,当天买入就是dp[i-1][0] - nums[i]
class Solution:
def maxProfit(self, prices: List[int]) -> int:
#dp[i][0] 第i天不持有股票 手里的最大现金
#dp[i][1] 第i天持有股票 手里的最大现金
# dp[i][0] = max(dp[i-1][0], dp[i - 1][1] + nums[i])
# dp[i][1] = max(dp[i-1][1], dp[i - 1][0] - nums[i])
# dp[0][0] = 0
# dp[0][1] = -nums[0]
dp = [[0] * 2 for i in range(len(prices))]
dp[0][1] = -prices[0]
for i in range(1, len(prices)):
dp[i][0] = max(dp[i-1][0], dp[i - 1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], 0 - prices[i])
return dp[-1][0]
78. 跳跃游戏
题目描述
解法
class Solution:
def canJump(self, nums: List[int]) -> bool:
if len(nums) == 1:
return True
max_length = 0 #每次更新一下最远能到的索引
i = 0
while i <= max_length: #i如果小于这个索引
max_length = max(max_length, i + nums[i]) #更新索引为更大的值
if max_length >= len(nums) - 1: #如果索引超过了数组最后的索引
return True #能到达
i += 1
return False #最后没到的话返回False
79. 跳跃游戏2
题目描述
解法
class Solution:
def jump(self, nums: List[int]) -> int:
if len(nums) == 1:
return 0
max_length = 0 #已遍历过的元素能到的最远距离
i = 0
count = 0
cur = 0 #当前步骤能到的最远距离
while(i < len(nums)):
max_length = max(max_length, i + nums[i])
if i == cur: #如果i走到了当前步骤能到的最远距离
cur = max_length #要跳跃了,更新cur为max_length
count += 1
if max_length >= len(nums) - 1: #如果max_length大于最后一个值的索引,返回结果
return count
i += 1
80.划分字母区间
题目描述
解法
class Solution:
def partitionLabels(self, s: str) -> List[int]:
max_right_index = [0] * 26 #哈希数组记录每个字符最右边到达的位置
for i in range(len(s)):
max_right_index[ord(s[i]) - ord('a')] = i #遍历记录
left, right = 0, 0 #两个指针用来计算区间长度
max_right = 0
result = [] #结果数组
while(right < len(s)): #右指针持续移动,每次更新已经遍历过的字符最右边的那个在哪
max_right = max(max_right, \
max_right_index[ord(s[right]) - ord('a')])
if right == max_right: #如果右指针走到了已经遍历过的字符最右边的那个
result.append(right - left + 1) #记录当前区间长度
left = right + 1 #更新区间七点
right += 1
return result
动态规划
81. 爬楼梯
题目描述
解法
class Solution:
def climbStairs(self, n: int) -> int:
# dp: 到达第i个台阶有多少种方法
# dp[i] = dp[i - 1] + dp[i - 2] 到达第i个台阶要么从i-1来,要么从i-2来
dp = [0] * (n + 2)
dp[1] = 1
dp[2] = 2
if n <= 2:
return n
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
82. 杨辉三角
题目描述
解法
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
#dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] #dp递推公式,每一行元素等于左上角加右上角
dp = [[1] * (i + 1) for i in range(numRows)] #初始化三角数组
if numRows < 3: #1行或者2行直接返回dp数组
return dp
for i in range(2, numRows): #大于两行,递推一下
for j in range(1, i):
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
return dp
83. 打家劫舍
题目描述
解法
class Solution:
def rob(self, nums: List[int]) -> int:
#dp[i] 偷到第i间房屋能偷到的最大金额
#dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]
if len(nums) == 1: #两个边界条件返回
return nums[0]
if len(nums) == 2:
return max(nums[0], nums[1])
dp = [0] * (len(nums) + 1) #dp数组表示偷到第i间房屋能偷到的最大金额
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1]) #初始化dp数组
for i in range(2, len(nums)):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]) #递推
return dp[len(nums) - 1]
拓展:
213 打家劫舍2️⃣ 首尾房屋相邻
class Solution:
def rob(self, nums: List[int]) -> int:
# dp[i] 偷到第i间屋子的最大金额
# dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
# 和第一题的区别在于首位房屋相邻,分别考虑两种情况 即偷第一家不偷最后一家和不偷第一家 偷最后一家
dp = [0] * (len(nums) + 1)
if len(nums) == 1:
return nums[0]
if len(nums) == 2:
return max(nums[0], nums[1])
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
# candidate_1 偷第一家不偷最后一家
for i in range(2, len(nums) - 1):
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
candidate_1 = dp[len(nums) - 2]
# candidate_2 不偷第一家偷最后一家
dp = [0] * (len(nums) + 1)
dp[0] = 0
dp[1] = nums[1]
for i in range(2, len(nums)):
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
candidate_2 = dp[len(nums) - 1]
return max(candidate_1, candidate_2)
337 打家劫舍3️⃣ 二叉树
84. 完全平方数
题目描述
解法
class Solution:
def numSquares(self, n: int) -> int:
dp = [0] + [float('inf')] * n #计算最小数量的dp数组初始化
for i in range(int(n ** 0.5) + 1): #遍历物品,物品是正整数的平方,由于int向下取整,所以+1
for j in range(i ** 2, n + 1): #遍历背包
dp[j] = min(dp[j], dp[j - i ** 2] + 1) #递推公式,求装满背包的最少数量
return dp[-1] if dp[-1] != float('inf') else -1 #如果不是完全平方数就return -1
85. 零钱兑换
题目描述
解法
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
# dp[j] 把背包装满的最少硬币个数
# dp[j] = min(dp[j - coin] + 1, dp[j]) 装当前硬币的最少个数 +1 和不装当前硬币的最少个数中取最小的
dp = [0] + [float('inf')] * amount
for coin in coins:
for j in range(coin, amount + 1):
dp[j] = min(dp[j], dp[j - coin] + 1)
if dp[-1] == float('inf'):
return -1
return dp[-1]
86. 单词拆分
题目描述
解法
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
#dp[j]: 从word中任取,能否能把0-j的背包{字符串切片}装满
dp = [True] + [False] * len(s)
for i in range(len(s) + 1):
for j in range(i + 1):
if dp[j] and s[j:i] in wordDict:
dp[i] = True
return dp[-1]
87. 最长递增子序列
题目描述
解法
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
#dp[j]以j结尾的最长递增子序列的长度
dp = [1] * len(nums) #初始化为1
for j in range(len(nums)): #顺序遍历J
for i in range(j): #每次需要找j前面所有的元素,以其结尾的最长递增子序列的长度,如果nums[j]大于结尾元素,更新当前长度为最长的那个+1
if nums[j] > nums[i]:
dp[j] = max(dp[j], dp[i] + 1) #这里需要max是要找前面更新过的dp[j]中的最长的,因为nums[j] > nums[i]的情况很多,之前dp[j]可能就已经更新过了,这里取一个max可以保证最后取到的是最长的
return max(dp)
88. 乘积最大子数组
题目描述
解法
class Solution:
def maxProduct(self, nums: List[int]) -> int:
max_i = nums[0] #记录当前遍历元素的最大乘积
min_i = nums[0] #记录当前遍历元素的最小乘积(考虑负数情况)
result = nums[0] #记录返回结果
for i in range(1, len(nums)):
mx = max_i #当前记录的最大乘积,用于下面的计算
mn = min_i #当前记录的最小乘积,同上
max_i = max( nums[i] * mx, nums[i], nums[i] * mn) #下一个最大乘积从上一个最大乘积乘以当前值、上一个最小乘积乘以当前值、当前值三个中选最大
min_i = min( nums[i] * mn, nums[i], nums[i] * mx) #更新下一个最小乘积
result = max(result, max_i) #记录最大结果
return result
89. 分割等和子集
题目描述
解法
class Solution:
def canPartition(self, nums: List[int]) -> bool:
val = sum(nums) / 2 #背包容量
if val != int(val): #如果背包容量不为整数,返回False
return False
val = int(val)
dp = [0] * (val+1) #初始化背包
for num in nums: #先遍历物品
for j in range(val, num - 1, -1): #后倒序(只能拿取一次0-1背包)遍历背包
dp[j] = max(dp[j], dp[j - num] + num) #经典递推公式,装与不装当前物品的最大价值,这里价值和重量都是num
return dp[-1] == val #背包的最大价值是否等于val,等于说明装满了
90. 最长有效括号
题目描述
解法
多维动态规划
91. 不同路径
题目描述
解法
92. 最小路径和
题目描述
解法
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
# dp[i][j] = min(dp[i-1][j], dp[i][j - 1]) + grid[i][j] 动规递推公式,dp[i][j]表示到达[i][j]的最小路径和,包含grid[i][j]
dp = [[0] * len(grid[0]) for _ in range(len(grid))]
####初始化dp数组,第一行累加,第一列累加
if len(grid) <= 1 and len(grid[0]) <=1 :
return grid[0][0]
cur = grid[0][0]
for i in range(1, len(grid)):
cur += grid[i][0]
dp[i][0] = cur
cur = grid[0][0]
for j in range(1, len(grid[0])):
cur += grid[0][j]
dp[0][j] = cur
#递推
for i in range(1, len(grid)):
for j in range(1, len(grid[0])):
dp[i][j] = min(dp[i-1][j], dp[i][j - 1]) + grid[i][j]
return dp[-1][-1]
93. 最长回文子串
题目描述
解法
class Solution:
def longestPalindrome(self, s: str) -> str:
max_len = 0 #记录当前遍历字串的长度
for j in range(len(s) + 1): #快指针一直走
for i in range(j + 1): #慢指针每次都从头遍历到快指针
st = s[i:j+1] #截出当前字串
if st == st[::-1] and len(st) >= max_len: #如果是回文子串并且长度大于已记录的长度
result = st #更新结果为当前最大字串
max_len = len(st) #更新最大长度
return result
94. 最长公共子序列
题目描述
解法
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
#dp[i][j] 以i-1 和j-1字符结尾的两个序列拥有的最长公共子序列长度
dp = [[0]*(len(text1) + 1) for _ in range(len(text2) + 1)]
#这里要+1是因为dp[i]表示以i-1结尾,所以最后一个dp[len(text1)]表示len(text1) - 1结尾的字符
for i in range(1, len(text2) + 1): #遍历到len(text) 索引
for j in range(1, len(text1) + 1):
if text2[i - 1] == text1[j - 1]: #如果i-1和j-1两个字符相等,因为dp数组定义,所以比较这两个
dp[i][j] = dp[i - 1][j - 1] + 1 #以i-1和j-1字符结尾的是i-1和j-1结尾的+1
else: #不相等的情况要考虑是因为本题可以不连续,所以在不连续的时候要把之前的最大值保留下来
dp[i][j] = max(dp[i][j - 1], dp[i - 1][j])
return dp[-1][-1]
95. 编辑距离
题目描述
解法
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
#dp[i][j]: 以i-1结尾的word1的字串变到以j-1结尾的word2字串的最小操作数
dp = [[0] * (len(word2) + 1) for _ in range(len(word1) + 1)]
# 初始化 空字符串变到另一个串需要的操作数
for i in range(len(word1) + 1):
dp[i][0] = i
for j in range(len(word2) + 1):
dp[0][j] = j
#和最长公共子序列类似
for i in range(1, len(word1) + 1):
for j in range(1, len(word2) + 1):
#如果下两个字符相等,不用操作
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
#不相等的话,从删word1 删word2和替换三种操作中选最小的+1
else:
dp[i][j] = min(dp[i - 1][j], dp[i - 1][j - 1], dp[i][j - 1]) + 1
return dp[-1][-1]
技巧
96. 只出现一次的数字
题目描述
解法
class Solution:
def singleNumber(self, nums: List[int]) -> int:
# ^异或算符,不同返回1 相同返回0
# 任何数和0异或还是这个数
# 这题就把所有数异或一下,最后剩下的就是那个独一无二的
# 异或还可以用来判断奇偶,异或1 返回1就是奇数 返回0就是偶数,因为二进制偶数的最低位肯定是0,而奇数是1,异或一下就判断奇偶了
cur = 0
for num in nums:
cur ^= num
return cur