导语
leetcode刷题笔记记录,主要记录题目包括:
知识点
图
在计算机科学和离散数学中,图是网络结构的一种抽象表示,用来表示对象之间的一些对应关系。图由一组节点(或称为顶点)和一组连接这些节点的边(或称为弧)组成。
分类
- 有向图(Directed Graph):边有方向。
- 无向图(Undirected Graph):边没有方向。
- 加权图(Weighted Graph):每条边都有一个权重。
- 无权图(Unweighted Graph):每条边权重相同。
- 有环图(Cyclic Graph):至少存在一个节点,可以从该节点出发沿若干条边回到该节点。
- 无环图(Acyclic Graph):没有任何环。
深度优先搜索(DFS)
定义
深度优先搜索(Depth-First Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会尽可能深地搜索树的分支。
应用示例
- 迷宫问题:找出从起点到终点的路径。
- 有向无环图(DAG)中的拓扑排序。
- 寻找图中的连通组件。
- 最短路径和最长路径问题。
- 网络爬虫中的网页遍历。
DFS 和回溯算法
回溯算法
回溯算法是一种通过探索所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个解的话,就会"回溯"以取消最后的计算步骤。
关系
- DFS 是一种特殊类型的回溯算法。在图和树的遍历中,如果走不通了,就会回溯到上一个节点,继续尝试其他分支。
- 回溯算法通常用于找到所有(或一部分)解,而深度优先搜索通常用于找到某一解。
- 在实现时,DFS 和回溯算法都使用递归来模拟全部可能的解。
通过深度优先搜索和回溯算法,可以解决许多复杂的计算问题,特别是与路径、排序和排列有关的问题。这两个算法在很多领域有广泛的应用,比如人工智能、机器学习、网络设计等。
Leetcode 797. 所有可能的路径
题目描述
给你一个有 n 个节点的 有向无环图(DAG) ,请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)
graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。
示例 1:
输入: graph = [[1,2],[3],[3],[]]
输出: [[0,1,3],[0,2,3]]
解释: 有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
示例 2:
输入: graph = [[4,3,1],[3,2,4],[3],[4],[]]
输出: [[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]
提示:
n == graph.length2 <= n <= 150 <= graph[i][j] < ngraph[i][j] != i(即不存在自环)graph[i]中的所有元素 互不相同- 保证输入为 有向无环图(DAG)
解法
使用深搜三部曲:
- 确认递归函数、参数:
# 定义一个深度优先搜索(DFS)方法
def dfs(self, graph: List[List[int]], root: int) -> None:
这里将设置两个全局变量:
- path:用于存储单次搜索的路径
- result:用于存储所有符合条件的路径
- 确认终止条件
# 如果当前的节点是 n-1(即图的终点)
if root == len(graph) - 1:
# 将当前路径添加到结果列表中
self.result.append(self.path[:])
return
- 处理目前搜索节点出发的路径
# 遍历从当前节点出发可达的所有节点
for i in graph[root]:
# 将下一个节点添加到当前路径中
self.path.append(i)
# 从下一个节点开始继续进行深度优先搜索
self.dfs(graph, i)
# 回溯:将刚才添加的节点从当前路径中移除
self.path.pop()
完整代码如下:
from typing import List
class Solution:
def __init__(self):
self.result = [] # 用于存储所有从 0 到 n-1 的路径
self.path = [0] # 用于存储当前正在探索的路径
# 定义一个深度优先搜索(DFS)方法
def dfs(self, graph: List[List[int]], root: int) -> None:
# 如果当前的节点是 n-1(即图的终点)
if root == len(graph) - 1:
# 将当前路径添加到结果列表中
self.result.append(self.path[:])
return
# 遍历从当前节点出发可达的所有节点
for i in graph[root]:
# 将下一个节点添加到当前路径中
self.path.append(i)
# 从下一个节点开始继续进行深度优先搜索
self.dfs(graph, i)
# 回溯:将刚才添加的节点从当前路径中移除
self.path.pop()
# 主函数
def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
# 从节点 0(起点)开始进行深度优先搜索
self.dfs(graph, 0)
# 返回所有从节点 0 到节点 n-1 的路径
return self.result
Leetcode 200. 岛屿数量
题目描述
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入: grid = [ ["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出: 1
示例 2:
输入: grid = [ ["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出: 3
提示:
m == grid.lengthn == grid[i].length1 <= m, n <= 300grid[i][j]的值为'0'或'1'
解法
这个问题是一个经典的图遍历问题,可以通过深度优先搜索(DFS)来解决。
这里的基本思路是:遍历整个二维网格,当我们遇到一个“1”(陆地)时,我们从这个点开始进行深度优先搜索,将与这个点相连的所有陆地(即整个岛屿)都标记为已访问(例如将“1”改为“0”或其他标记),以避免重复计算。同时,岛屿计数加1。
以下是使用DFS实现的Python代码:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
# 初始化岛屿数量为 0
count = 0
# 获取网格的行数和列数
m, n = len(grid), len(grid[0])
# 定义深度优先搜索函数
def dfs(i, j):
# 判断边界条件以及是否为陆地('1')
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != '1':
return
# 将访问过的陆地标记为 '2',以避免重复访问
grid[i][j] = '2'
# 对相邻的四个方向进行深度优先搜索
dfs(i - 1, j) # 向上
dfs(i + 1, j) # 向下
dfs(i, j - 1) # 向左
dfs(i, j + 1) # 向右
# 遍历整个网格
for i in range(m):
for j in range(n):
# 当遇到一个未访问的陆地('1')时,进行深度优先搜索,并增加岛屿数量
if grid[i][j] == '1':
dfs(i, j)
count += 1
# 返回最终的岛屿数量
return count
Leetcode 695. 岛屿的最大面积
题目描述
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
示例 1:
输入: grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出: 6
解释: 答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
示例 2:
输入: grid = [[0,0,0,0,0,0,0,0]]
输出: 0
提示:
m == grid.lengthn == grid[i].length1 <= m, n <= 50grid[i][j]为0或1
解法
使用dfs,将比那里的位置标记为2,之后只要递归访问四个方向,并将它们的面积相加,加 1 表示当前陆地的面积
return 1 + dfs(i - 1, j) + dfs(i + 1, j) + dfs(i, j - 1) + dfs(i, j + 1)
在具体调用时,定义一个最大面积,每次将返回的岛屿面积和最大面积比较,记录最新的最大面积。完整版代码如下:
from typing import List
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
max_area = 0 # 初始化最大岛屿面积为 0
m, n = len(grid), len(grid[0]) # 获取网格的行数和列数
# 定义 DFS 函数
def dfs(i, j):
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != 1: # 边界检查和陆地检查
return 0 # 返回 0,表示不增加面积
else:
grid[i][j] = 2 # 将已经访问的陆地标记为 2
# 递归访问四个方向,并将它们的面积相加,加 1 表示当前陆地的面积
return 1 + dfs(i - 1, j) + dfs(i + 1, j) + dfs(i, j - 1) + dfs(i, j + 1)
for i in range(m):
for j in range(n):
if grid[i][j] == 1: # 当找到一个未访问的陆地时
max_area = max(max_area, dfs(i, j)) # 更新最大岛屿面积
return max_area # 返回最大岛屿面积