数组双指针哈希
1. 俩数之和
俩数之和
数组、哈希表
题目:
思路:
代码:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
map_dict = dict()
for index,value in enumerate(nums):
if (target - value) in map_dict:
return [map_dict[target - value],index]
else:
map_dict[value] = index
return []
2. 移动零
移动零
数组、双指针
题目:
代码:
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
fast = 0
slow = 0
while fast < n:
if nums[fast] != 0:
nums[slow] = nums[fast]
slow += 1
fast += 1
while slow < n:
nums[slow] = 0
slow += 1
3. 无重复字符的最长子串
无重复字符的最长字串
哈希表、字符串、滑动窗口
题目:
思路:
采用集合来判断是否有重复字符
代码:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
occ = set()
result = 0
rk = 0
for i in range(len(s)):
if i != 0:
occ.remove(s[i-1])
while rk < len(s) and s[rk] not in occ:
occ.add(s[rk])
rk += 1
result = max(result,rk - i)
return result
4. 和为K的子数组
和为K的子数组
数组
哈希表
前缀和
题目:
思路:
前缀和+哈希表
代码:
'''
1. collections.defaultdict(int):defaultdict 是 dict 的一个子类,它允许指定一个默认的工厂函数,用来生成默认值。
在这里,我们指定的默认值工厂函数是 int,意味着当你访问一个不存在的键时,会返回一个 int 类型的默认值,即 0。
'''
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
ans = 0
presum = collections.defaultdict(int) # 创建了一个默认值为 0 的 defaultdict 对象,用于存储前缀和及其出现的次数
presum[0] = 1 # 初始化前缀和为 0 的个数为 1
cur_presum = 0 # 初始化当前前缀和为 0,用来记录当前位置的前缀和
for n in nums:
cur_presum += n
ans += presum[cur_presum-k] # 累加 cur_presum - k 在 presum 中出现的次数
presum[cur_presum] += 1 # 更新 presum[cur_presum] 的值,表示当前前缀和出现的次数加 1
return ans
5. 最大子数组和
最大子数组和
数组
分治
动态规划
题目:
思路:
采用贪心策略,如果 -2 1 在一起,计算起点的时候,一定是从 1 开始计算,因为负数只会拉低总和,这就是贪心贪的地方!
局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
全局最优:选取最大“连续和”
代码:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
result = float('-inf')
count = 0
for i in range(len(nums)):
count += nums[i]
if count > result:
result = count
if count <= 0:
count = 0
return result
6. 矩阵置零
矩阵置零
数组
哈希表
矩阵
题目:
思路:
代码:
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
m = len(matrix)
n = len(matrix[0])
m_list = [False] * m
n_list = [False] * n
for i in range(m):
for j in range(n):
if matrix[i][j] == 0:
m_list[i] = True
n_list[j] = True
for i in range(m):
for j in range(n):
if m_list[i] or n_list[j]:
matrix[i][j] = 0
7. 相交链表
相交链表
题目:
思路:
代码:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
A = headA
B = headB
while A != B:
if A:
A = A.next
else:
A = headB
if B:
B = B.next
else:
B = headA
return A
8. 字母异位词分组
字母异位词分组
题目:
代码:
'''
思路:
1.先对列表中的字符串进行排序
2.将具有相同字母的字符串以列表的形式放入字典中
3.最后将字典中的值以列表的形式输出
'''
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
n = len(strs)
record = dict()
if n == 0:return [strs]
for i in strs:
s = str(sorted(i))
if s not in record:
record[s] = [i]
else:
record[s].append(i)
return list(record.values())
9. 最长连续序列
最长连续序列
数组 哈希表
题目:
思路:
代码:
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
ans = 0 # 记录最长连续序列的长度
record = set(nums) # 记录nums中的所有数值
for num in record:
# 如果当前的数是一个连续序列的起点,统计这个连续序列的长度
if (num - 1) not in record:
count = 1 # 连续序列的长度,初始为1
while (num + 1) in record:
count += 1
num += 1 # 不断查找连续序列,直到num的下一个数不存在于数组中
ans = max(ans, count) # 更新最长连续序列长度
return ans
10. 盛最多水的容器
盛最多水的容器
贪心 数组 双指针
题目:
思路:
代码:
class Solution:
def maxArea(self, height: List[int]) -> int:
left = 0
right = len(height) - 1
res = 0
while left < right:
if height[left] < height[right]:
res = max(res, height[left] * (right - left))
left += 1
else:
res = max(res, height[right] * (right - left))
right -= 1
return res
11. 三数之和
三数之和
数组
双指针
排序
题目:
思路:
双指针法
1)首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
2)依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
3)接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
4)如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
时间复杂度:O(n^2)
空间复杂度: O(1)
代码:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
n = len(nums)
res = []
for i in range(n):
if nums[0] > 0:
return []
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = n - 1
while left < right:
if nums[i] + nums[left] + nums[right] > 0:
right -= 1
elif nums[i] + nums[left] + nums[right] < 0:
left += 1
else:
res.append([nums[i],nums[left],nums[right]])
while left < right and nums[left + 1] == nums[left]:
left += 1
while left < right and nums[right - 1] == nums[right]:
right -= 1
left += 1
right -= 1
return res
12. 找到字符串中所有字母异位词
找到字符串中所有字母异位词
哈希表
字符串
滑动窗口
题目:
思路:
代码:
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
res = []
s_num = [0] * 26
p_num = [0] * 26
s_len = len(s)
p_len = len(p)
if s_len < p_len: return []
for i in range(p_len):
s_num[ord(s[i]) - ord('a')] += 1
p_num[ord(p[i]) - ord('a')] += 1
if s_num == p_num:res.append(0)
for i in range(p_len,s_len):
s_num[ord(s[i]) - ord('a')] += 1
s_num[ord(s[i - p_len]) - ord('a')] -= 1
if s_num == p_num:res.append(i - p_len + 1)
return res
13. 合并区间
合并区间
数组 排序
思路:
代码:
class Solution:
def merge(self, intervals):
result = []
if len(intervals) == 0:
return result # 区间集合为空直接返回
intervals.sort() # 默认按照区间的左边界进行排序
result.append(intervals[0]) # 第一个区间可以直接放入结果集中
for i in range(1, len(intervals)):
if result[-1][1] >= intervals[i][0]: # 发现重叠区间
# 合并区间,只需要更新结果集最后一个区间的右边界,因为根据排序,左边界已经是最小的
result[-1][1] = max(result[-1][1], intervals[i][1])
else:
result.append(intervals[i]) # 区间不重叠
return result
14. 轮转数组
轮转数组
数组
题目:
思路:
题目的前提是需要在原数组上进行修改,没有返回值
先对数组进行切片,对切片后的数组进行遍历,对原数组进行值的修改
代码:
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k = k % n # 有可能k值会大于数组长度,因此需要对k除n取余
result1 = nums[n - k:]
result2 = nums[:n-k]
for i in range(len(result1)):
nums[i] = result1[i]
for j in range(len(result2)):
nums[k + j] = result2[j]
15. 除自身以外数组的乘积
除自身以为数组的乘积
数组、前缀和
题目:
思路:
本题的难点在于 不能使用除法 ,即需要 只用乘法 生成数组 ans
。根据题目对 ans[i]
的定义,可列出下图所示的表格。
根据表格的主对角线(全为 1 ),可将表格分为 上三角 和 下三角 两部分。分别迭代计算下三角和上三角两部分的乘积,即可 不使用除法 就获得结果。
下图中 A=numsA , B=ansB
算法流程:
代码:
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
ans, tmp = [1] * len(nums), 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
矩阵
1. 螺旋矩阵
螺旋矩阵
数组、矩阵、模拟
题目:
代码:
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix: return []
l, r, t, b, res = 0, len(matrix[0]) - 1, 0, len(matrix) - 1, []
while True:
for i in range(l, r + 1): res.append(matrix[t][i]) # left to right
t += 1
if t > b: break
for i in range(t, b + 1): res.append(matrix[i][r]) # top to bottom
r -= 1
if l > r: break
for i in range(r, l - 1, -1): res.append(matrix[b][i]) # right to left
b -= 1
if t > b: break
for i in range(b, t - 1, -1): res.append(matrix[i][l]) # bottom to top
l += 1
if l > r: break
return res
2. 旋转图像
旋转图像
数组、数学、矩阵
题目:
思路:
利用辅助矩阵
代码:
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
n = len(matrix)
# 深拷贝 matrix -> tmp
tmp = copy.deepcopy(matrix)
# 根据元素旋转公式,遍历修改原矩阵 matrix 的各元素
for i in range(n):
for j in range(n):
matrix[j][n - 1 - i] = tmp[i][j]
3. 搜索二维矩阵 II
搜索二维矩阵||
数组、二分查找、分治、矩阵
题目:
思路:
代码:
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
i = len(matrix) - 1
j = 0
while i >= 0 and j < len(matrix[0]):
num = matrix[i][j]
if num < target:
j += 1
elif num > target:
i -= 1
else:
return True
return False
链表
1. 反转链表
反转链表
链表
题目:
思路:
双指针迭代
代码:
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur = head
pre = None
while cur:
temp = cur.next
cur.next = pre
pre = cur
cur = temp
return pre
2. 回文链表
回文链表
栈、递归、链表、双指针
题目:
思路:
首先将链表的中的值转化为数组,然后在数组上判断是否为回文数组
代码:
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
if not head:return False
if not head.next:return True
cur = head
record = []
while cur:
record.append(cur.val)
cur = cur.next
return record == record[::-1]
3. 环形链表
环形链表
链表
双指针
题目:
思路:
定义俩个指针,一个慢指针一个快指针,慢指针一次移动一个节点,快指针一次移动俩个节点,如果存在环形,那会俩个指针必定会第二次相遇。
代码:
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:return True
return False
4. 环形链表||
环形链表||
链表
双指针
题目:
思路:
代码:
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast = head
slow = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
index1 = head
index2 = fast
while index1 != index2:
index1 = index1.next
index2 = index2.next
return index1
return None
5. 合并两个有序链表
合并俩个有序链表
链表
题目:
代码:
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
if not list1 and not list2:return None
if list1 and not list2:
return list1
if list2 and not list1:
return list2
cur1 = list1
cur2 = list2
if cur1.val < cur2.val:
head = cur1
cur1 = cur1.next
else:
head = cur2
cur2 = cur2.next
tmp = head
while cur1 and cur2:
if cur1.val <= cur2.val:
tmp.next = cur1
tmp = tmp.next
cur1 = cur1.next
elif cur1.val > cur2.val:
tmp.next = cur2
tmp = tmp.next
cur2 = cur2.next
while cur2:
tmp.next = cur2
tmp = cur2
cur2 = cur2.next
while cur1:
tmp.next = cur1
tmp = cur1
cur1 = cur1.next
return head
6. 俩数相加
俩数相加
链表
数学
题目:
思路:
- 同时遍历俩个链表
- 采用一个记录标签tmp
代码:
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
cur_l1 = l1
cur_l2 = l2
node = ListNode()
head = node
cur = head
tmp = 0
while cur_l1 and cur_l2:
node = ListNode()
if cur_l1.val + cur_l2.val + tmp >= 10:
node.val = (cur_l1.val + cur_l2.val + tmp) % 10
tmp = 1
else:
node.val = cur_l1.val + cur_l2.val + tmp
tmp = 0
cur.next = node
cur = cur.next
cur_l1 = cur_l1.next
cur_l2 = cur_l2.next
while cur_l1:
node = ListNode()
if cur_l1.val + tmp >= 10:
node.val = (cur_l1.val + tmp) % 10
tmp = 1
else:
node.val = cur_l1.val + tmp
tmp = 0
cur.next = node
cur = cur.next
cur_l1 = cur_l1.next
while cur_l2:
node = ListNode()
if cur_l2.val + tmp >= 10:
node.val = (cur_l2.val + tmp) % 10
tmp = 1
else:
node.val = cur_l2.val + tmp
tmp = 0
cur.next = node
cur = cur.next
cur_l2 = cur_l2.next
if tmp == 1:
node = ListNode(1)
cur.next = node
cur = cur.next
cur = cur.next
return head.next
7. 删除链表的倒数第 N 个结点
删除链表的倒数第 N 个结点
链表
双指针
题目:
思路:
- 滑动窗口
代码:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
node = ListNode()
node.next = head
cur_pre = node
head2 = node
cur = head
tmp = cur_pre
while n > 0:
tmp = tmp.next
n -= 1
while tmp.next:
tmp = tmp.next
cur_pre = cur
cur = cur.next
cur_pre.next = cur.next
return head2.next
8. 两两交换链表中的节点
题目:
思路:
代码:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy_head = ListNode(next=head)
current = dummy_head
# 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
while current.next and current.next.next:
temp = current.next # 防止节点修改
temp1 = current.next.next.next
current.next = current.next.next
current.next.next = temp
temp.next = temp1
current = current.next.next
return dummy_head.next
9. 排序链表
题目:
思路:
- 将链表转为数组
- 对数组进行排序
- 将排序的数组转为链表
代码:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:return head
cur = head
record = []
while cur:
record.append(cur.val)
cur = cur.next
record.sort()
node = ListNode()
head1 = node
cur1 = head1
for i in range(len(record)):
node = ListNode(val = record[i])
cur1.next = node
cur1 = cur1.next
return head1.next
二叉树
1. 二叉树的中序遍历
题目:
代码:
# 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 inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:return []
left = self.inorderTraversal(root.left)
right = self.inorderTraversal(root.right)
return left + [root.val] + right
2. 二叉树的最大深度
题目:
思路:
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
本题中我们通过后序求的根节点高度来求的二叉树最大深度。
【注】在解二叉树的题目时,优先考虑能否使用后序遍历
代码:
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root: return 0
left = self.maxDepth(root.left) # 左
right = self.maxDepth(root.right) # 右
height = 1 + max(left,right) # 根
return height
3. 翻转二叉树
题目:
思路:
采用前序遍历,交换左右子树
代码:
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root: return root
root.left,root.right = root.right,root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
4. 对称二叉树
题目:
思路:
对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
代码:
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
return self.compare(root.left, root.right)
def compare(self, left, right):
#首先排除空节点的情况
if left == None and right != None: return False
elif left != None and right == None: return False
elif left == None and right == None: return True
#排除了空节点,再排除数值不相同的情况
elif left.val != right.val: return False
#此时就是:左右节点都不为空,且数值相同的情况
#此时才做递归,做下一层的判断
outside = self.compare(left.left, right.right) #左子树:左、 右子树:右
inside = self.compare(left.right, right.left) #左子树:右、 右子树:左
isSame = outside and inside #左子树:中、 右子树:中 (逻辑处理)
return isSame
5. 二叉树的直径
题目:
代码:
class Solution:
def diameterOfBinaryTree(self, root: TreeNode) -> int:
self.ans = 1
def depth(root):
if not root: return 0
L = depth(root.left)
R = depth(root.right)
self.ans = max(self.ans, L + R + 1)
return max(L, R) + 1
depth(root)
return self.ans - 1
6. 二叉树的层序遍历
题目:
思路:
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
代码:
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
queue = collections.deque([root])
result = []
while queue:
level = []
for _ in range(len(queue)):
cur = queue.popleft()
level.append(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
result.append(level)
return result
7. 二叉树的直径
题目:
思路:
代码:
# 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 diameterOfBinaryTree(self, root: TreeNode) -> int:
ans = 0
def dfs(root):
if not root:
return -1
left_len = dfs(root.left)
right_len = dfs(root.right)
nonlocal ans
ans = max(ans,left_len + right_len + 2)
return max(left_len,right_len) + 1
dfs(root)
return ans
8. 将有序数组转换为二叉搜索树
将有序数组转换为二叉搜索树
题目:
思路:
本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间。
分割点就是数组的中间节点
代码:
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
def traversal(nums,left,right):
if left > right:
return None
mid = left + (right - left) // 2
root = TreeNode(nums[mid])
root.left = traversal(nums,left,mid - 1)
root.right = traversal(nums,mid + 1,right)
return root
return traversal(nums,0,len(nums) - 1)
9. 验证二叉搜索树
验证二叉搜索树
题目:
思路:
中序遍历二叉搜索树得到的数组结果是有序递增数组。采用双指针来判断节点的值是否符合二叉搜索树。同时要保证根节点要大于左子树所有节点,小于右子树所有节点。
代码:
class Solution:
def __init__(self):
self.pre = None
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
left = self.isValidBST(root.left)
if self.pre is not None and self.pre.val >= root.val:
return False
self.pre = root
right = self.isValidBST(root.right)
return left and right
10. 二叉搜索树中第K小的元素
二叉搜索树中第K小的元素
题目:
思路:
代码:
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
def dfs(root):
if not root:
return
dfs(root.left)
if self.k == 0:
return
self.k -= 1
if self.k == 0:
self.res = root.val
dfs(root.right)
self.k = k
dfs(root)
return self.res
11. 二叉树的右视图
二叉树的右视图
题目:
思路:
代码:
class Solution:
def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
ans = []
def f(node,depth):
if not node:
return
if depth == len(ans):
ans.append(node.val)
f(node.right,depth + 1)
f(node.left,depth + 1)
f(root,0)
return ans
12. 二叉树展开为链表
二叉树展开为链表
题目:
思路:
代码:
class Solution:
def flatten(self, root: Optional[TreeNode]) -> None:
# 定义一个辅助函数来处理展开,接收当前节点和前一个处理的节点
def flattenTree(node, prev):
if not node:
return prev
# 由于需要首先连接右侧节点,所以先处理右子树
prev = flattenTree(node.right, prev)
# 然后处理左子树
prev = flattenTree(node.left, prev)
# 更新当前节点的右节点为前一个节点,左节点为 None
node.right = prev
node.left = None
# 将当前节点设为新的前一个节点
return node
flattenTree(root, None)
13. 从中序与后序遍历序列构造二叉树
- 题目:
- 思路:
以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
中序数组的切割点怎么找?
根据后序数组最后一个元素来进行切割
后序数组的切割点怎么找?
后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了。
- 代码:
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
# 第一步: 特殊情况讨论: 树为空. (递归终止条件)
if not postorder:
return None
# 第二步: 后序遍历的最后一个就是当前的中间节点.
root_val = postorder[-1]
root = TreeNode(root_val)
# 第三步: 找切割点.
separator_idx = inorder.index(root_val)
# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
inorder_left = inorder[:separator_idx]
inorder_right = inorder[separator_idx + 1:]
# 第五步: 切割postorder数组. 得到postorder数组的左,右半边.
# ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的.
postorder_left = postorder[:len(inorder_left)]
postorder_right = postorder[len(inorder_left): len(postorder) - 1]
# 第六步: 递归
root.left = self.buildTree(inorder_left, postorder_left)
root.right = self.buildTree(inorder_right, postorder_right)
# 第七步: 返回答案
return root
14. 从前序与中序遍历序列构造二叉树
- 思路:
跟13.思路一样,这里先根据前序数组找到根节点来切分中序数组,然后根据切分的中序数组再切分前序数组。 - 代码:
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
# 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件
if not preorder:
return None
# 第二步: 前序遍历的第一个就是当前的中间节点.
root_val = preorder[0]
root = TreeNode(root_val)
# 第三步: 找切割点.
separator_idx = inorder.index(root_val)
# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
inorder_left = inorder[:separator_idx]
inorder_right = inorder[separator_idx + 1:]
# 第五步: 切割preorder数组. 得到preorder数组的左,右半边.
# ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的.
preorder_left = preorder[1:1 + len(inorder_left)]
preorder_right = preorder[1 + len(inorder_left):]
# 第六步: 递归
root.left = self.buildTree(preorder_left, inorder_left)
root.right = self.buildTree(preorder_right, inorder_right)
# 第七步: 返回答案
return root
15. 路径总和 III
- 题目:
- 思路:
- 代码:
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
ans = 0
cnt = defaultdict(int)
cnt[0] = 1
def dfs(node: Optional[TreeNode], s: int) -> None:
if node is None:
return
nonlocal ans
s += node.val
ans += cnt[s - targetSum]
cnt[s] += 1
dfs(node.left, s)
dfs(node.right, s)
cnt[s] -= 1 # 恢复现场
dfs(root, 0)
return ans
16. 二叉树的最近公共祖先
- 题目:
- 思路:
- 代码:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root == p or root == q or root is None:
return root
left = self.lowestCommonAncestor(root.left,p,q)
right = self.lowestCommonAncestor(root.right,p,q)
if left is not None and right is not None:
return root
if left is None and right is not None:
return right
elif left is not None and right is None:
return left
else:
return None
回溯
1. 全排列
- 题目:
- 思路:
树形结构图:
取得的结果集在叶子节点,回溯的终止条件中要加return,如果是取每个节点的值则不需要加return
注:采用一个数组来记录数值是否已经使用过 - 代码:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
path = []
result = []
used = [False] * len(nums)
def backtracking(nums,used,path,result):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if used[i]:
continue
used[i] = True
path.append(nums[i])
backtracking(nums,used,path,result)
path.pop()
used[i] = False
backtracking(nums,used,path,result)
return result
2. 子集
- 题目:
- 思路:
子集的抽取树形图:
- 代码:
class Solution:
def subsets(self, nums):
result = []
path = []
self.backtracking(nums, 0, path, result)
return result
def backtracking(self, nums, startIndex, path, result):
result.append(path[:]) # 收集子集,要放在终止添加的上面,否则会漏掉自己
# if startIndex >= len(nums): # 终止条件可以不加
# return
for i in range(startIndex, len(nums)):
path.append(nums[i])
self.backtracking(nums, i + 1, path, result)
path.pop()
3. 电话号码的字母组合
- 题目:
- 思路:
回溯,递归 - 代码:
class Solution:
def __init__(self):
self.letterMap = [
"", # 0
"", # 1
"abc", # 2
"def", # 3
"ghi", # 4
"jkl", # 5
"mno", # 6
"pqrs", # 7
"tuv", # 8
"wxyz" # 9
]
self.result = []
self.s = ""
def backtracking(self, digits, index):
if index == len(digits):
self.result.append(self.s)
return
digit = int(digits[index]) # 将索引处的数字转换为整数
letters = self.letterMap[digit] # 获取对应的字符集
for i in range(len(letters)):
self.s += letters[i] # 处理字符
self.backtracking(digits, index + 1) # 递归调用,注意索引加1,处理下一个数字
self.s = self.s[:-1] # 回溯,删除最后添加的字符
def letterCombinations(self, digits):
if len(digits) == 0:
return self.result
self.backtracking(digits, 0)
return self.result
4. 组合总和
- 题目:
- 思路:
注意for循环的起始值和终止值 - 代码:
class Solution:
def backtracking(self, candidates, target, total, startIndex, path, result):
if total > target:
return
if total == target:
result.append(path[:])
return
for i in range(startIndex, len(candidates)):
total += candidates[i]
path.append(candidates[i])
self.backtracking(candidates, target, total, i, path, result) # 不用i+1了,表示可以重复读取当前的数
total -= candidates[i]
path.pop()
def combinationSum(self, candidates, target):
result = []
self.backtracking(candidates, target, 0, 0, [], result)
return result
二分查找
1. 搜索插入位置
- 题目
- 代码:
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return left
2. 搜索二维矩阵
- 题目:
- 代码:
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
m,n = len(matrix),len(matrix[0])
left,right = 0,m*n - 1
while left <= right:
mid = (left + right) // 2
x = matrix[mid // n][mid % n]
if x == target:
return True
if x < target:
left = mid + 1
else:
right = mid - 1
return False
3. 在排序数组中查找元素的第一个和最后一个位置
- 题目:
- 代码:
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def search(nums,target):
left,right = 0,len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return left
start = search(nums,target)
if start == len(nums) or nums[start] != target:
return [-1,-1]
end = search(nums,target + 1) - 1
return [start, end]
- 思路:
先用二分法找到目标值的起始位置,然后在寻找末位置
4. 搜索旋转排序数组
- 题目
- 思路:
- 代码:
class Solution:
def search(self, nums: List[int], target: int) -> int:
def findMin(nums):# 寻找旋转排序数组中的最小值
left,right = -1,len(nums) - 1
while left + 1 < right:
mid = (left + right) // 2
if nums[mid] < nums[-1]:
right = mid
else:
left = mid
return right
def binary_search(nums,target,left,right):# 二分查找
while left + 1 < right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid
else:
right = mid
return right if nums[right] == target else -1
i = findMin(nums) # 获取最小值的下标
if target > nums[-1]:
return binary_search(nums,target,-1,i) # 在第一段
return binary_search(nums, target, i-1, len(nums)) # 在第二段
5. 寻找旋转排序数组中的最小值
- 题目:
- 思路:
- 代码:
class Solution:
def findMin(self, nums: List[int]) -> int:
left,right = -1,len(nums) - 1
while left + 1 < right:
mid = (left + right) // 2
if nums[mid] < nums[-1]:
right = mid
else:
left = mid
return nums[right]