1.路径总和III
- 思路: 前缀和 + 哈希 + DFS +回溯
- 代码链接
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def pathSum(self, root, targetSum):
"""
:type root: TreeNode
:type targetSum: int
:rtype: int
"""
# 思路:前缀和+哈希
# 二叉树前缀和相当于从根结点开始的路径和
# 1.前缀和数组中 s[0] = 0 —— 要把任意路径和都表示成两个前缀和的差 必须增加0
# 时间复杂度 o(N) 空间复杂度 o(N)
global ans
ans = 0
cnt = defaultdict(int) # 初始化哈希表
cnt[0] = 1 # 前缀和数组 s[0] = 1 空路径数量为1
# 二叉树遍历 用深度优先
def dfs(node, s):
if not node: return node # 空节点返回
s += node.val # 计算前缀和
global ans # 申明全局变量
ans += cnt[s - targetSum] # 先更新结果 再更新数据 否则当targetSum=0的时候 会导致多计算一条数据
cnt[s] += 1 # 前缀和为s的路径增加一条
# 递归访问当前节点的左右子节点
dfs(node.left, s)
dfs(node.right, s)
# 完成当前节点的处理后,进行回溯,即将当前前缀和的计数减1
cnt[s] -= 1 # 恢复现场
# 如果不恢复现场,当我们递归完左子树,要递归右子树时,cnt 中还保存着左子树的数据。
# 但递归到右子树,要计算的路径并不涉及到左子树的任何节点,如果不恢复现场,cnt 中统计的前缀和个数会更多,我们算出来的答案可能比正确答案更大。
dfs(root, 0)
return ans
2.二叉树的最近公共祖先
- 代码链接
- 时间复杂度:O(n),其中 n 为二叉树的节点个数。
空间复杂度:O(n)。最坏情况下,二叉树是一条链,因此递归需要 O(n) 的栈空间。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def lowestCommonAncestor(self, root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
# 1.递归实现 判断返回值应该为树的节点 就是最近的公共祖先
# 2.判断递归终止条件 就是什么时候开始返回
# 几种情况 首先是(1)root为空 返回None就错误了 而是返回节点
# (2)找到了p或者q,注意p,q不是一样的值 只要返回该节点就行
if not root: return root
elif root == p: return root
elif root == q: return root
# 后序遍历需要对返回值做进一步的逻辑处理 (从下向上处理就需要后序遍历)
# 因此需要用left和right来进行存储
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
# 只有右子树找到 返回右子树结果
if not left and right:
return right
# 只有左子树找到 返回左子树结果
elif not right and left:
return left
# 左右子树都没找到 返回空节点
elif not left and not right:
return left
# 左右子树都找到 返回当前结果
else:
return root
3.二叉树中的最大路径和
- 代码链接
- 链:从叶子到当前节点的路径。其节点值之和是 dfs 的返回值。
- 直径:等价于由两条(或者一条)链拼成的路径。我们枚举每个 node,假设直径在这里「拐弯」,也就是计算由左右两条从叶子到 node 的链的节点值之和,去更新答案的最大值。
⚠注意:dfs 返回的是链的节点值之和,不是直径的节点值之和。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def maxPathSum(self, root):
"""
:type root: TreeNode
:rtype: int
"""
global ans
ans = float("-inf")
def dfs(node):
if node is None:
return 0
l_val = dfs(node.left)
r_val = dfs(node.right)
global ans
ans = max(ans, l_val + r_val + node.val) # 最大路径和要包括左右子链长度 + 本身结点值
# 注意 深度遍历的时候返回的是 链长 而不是路径长
# 此外由于要找最大子路径 返回的左右子链长<0 的时候直接截断
return max(max(l_val, r_val) + node.val, 0)
dfs(root)
return ans
4.岛屿数量
class Solution(object):
def numIslands(self, grid):
"""
:type grid: List[List[str]]
:rtype: int
"""
# 思路:
# 如果遇到格子为1的岛屿就以该岛屿为中心开始深度搜索,将可以访问的岛屿都置为其余数字 并且count + 1
def dfs(grid, i, j):
# 如果越界了则返回
if not 0 <= i < len(grid) or not 0<= j < len(grid[0]): return
# 如果此时遇到了海洋则返回
elif grid[i][j] == '0':
return
# !!! 修改当前访问到节点的值
grid[i][j] = '0'
# 访问相邻节点
dfs(grid, i - 1, j)
dfs(grid, i + 1, j)
dfs(grid, i, j + 1)
dfs(grid, i, j - 1)
count = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == "1":
dfs(grid, i, j) # 传入参数必须指示我们现在需要搜索的格子在哪里
count += 1
return count
5.腐烂的橘子
class Solution(object):
def orangesRotting(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
# 思路:
# 这里的腐烂橘子 相当于层序遍历网格,如果层序遍历结束后任然存在未腐烂 则是-1
m, n = len(grid), len(grid[0])
fresh = 0
q = []
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == 1:
fresh += 1
if grid[i][j] == 2:
q.append((i, j)) # 把一开始就腐烂的橘子放进去
ans = -1
while q:
ans += 1 # 每一层就加1 代表经过1分钟
tmp = q
q = []
for x,y in tmp: # 取出已经腐烂的橘子开始腐烂其他
for i, j in (x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1):
if 0 <= i < m and 0 <= j < n and grid[i][j] == 1:
fresh -= 1 # 新鲜橘子数-1
grid[i][j] = 2
q.append((i, j)) # 把该层访问的橘子都放进去
# max(ans, 0) 避免初始情况没有新鲜橘子和腐烂橘子的情况,这个时候fresh=0,ans = -1
return -1 if fresh else max(ans, 0)
6.课程表
- 代码链接
- 拓扑排序
- 思路是通过 拓扑排序 判断此课程安排图是否是 有向无环图(DAG) 。 拓扑排序原理: 对 DAG 的顶点进行排序,使得对每一条有向边 (u,v),均有 u(在排序记录中)比 v 先出现。亦可理解为对某点 v 而言,只有当 v 的所有源点均出现了,v 才能出现。
class Solution(object):
def canFinish(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: bool
"""
# 记录每个课程的入度情况
inDegree = [0] * numCourses
course_map = [[] for _ in range(numCourses)]
# prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
# 那么 ai 的入度+1
for a,b in prerequisites:
inDegree[a] += 1
course_map[b].append(a) # 说明要学习a就必须学b,那么存储下来 b是哪些课程的先修课程
queue = deque()
# 找到所有入度为0的所有课程可以先修
for i in range(len(inDegree)):
if not inDegree[i]:
queue.append(i) # 把先修课程都先取出放入队列
while queue:
pre = queue.popleft()
numCourses -= 1
for cur in course_map[pre]:
inDegree[cur] -= 1 # 所以pre修完后 对应的 cur课程的入度-1
if not inDegree[cur]: # 如果此时课程入度为0 则可以加入 为新的先修课程
queue.append(cur)
return not numCourses # 如果numCourses=0 说明修完了,否则未修完课程