代码随想录训练营 Day52打卡 图论part03
一、卡码101. 孤岛的总面积
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域,且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。
现在你需要计算所有孤岛的总面积,岛屿面积的计算方式为组成岛屿的陆地的总数。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0。
输出描述
输出一个整数,表示所有孤岛的总面积,如果不存在孤岛,则输出 0。
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
1
提示信息
在矩阵中心部分的岛屿,因为没有任何一个单元格接触到矩阵边缘,所以该岛屿属于孤岛,总面积为 1。
本题要求找到不靠边的陆地面积,那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图 统计此时还剩下的陆地就可以了。
如图,在遍历地图周围四个边,靠地图四边的陆地,都为绿色,
在遇到地图周边陆地的时候,将1都变为0,此时地图为这样:
然后我们再去遍历这个地图,遇到有陆地的地方,去采用深搜或者广搜,边统计所有陆地。
代码实现
from collections import deque
# 处理输入
n, m = list(map(int, input().strip().split())) # 输入 n 行 m 列
g = [] # 网格 g 用于存储输入的二进制矩阵
for _ in range(n):
row = list(map(int, input().strip().split())) # 输入每一行的陆地/水域数据
g.append(row) # 将每一行添加到网格 g 中
# 定义四个方向:右、下、上、左,分别表示 (0, 1), (1, 0), (-1, 0), (0, -1)
directions = [[0, 1], [1, 0], [-1, 0], [0, -1]]
count = 0 # 定义全局变量 count,用于记录每次遍历连通陆地的面积
# 广度优先搜索函数 BFS
def bfs(r, c):
"""
使用 BFS 遍历从 (r, c) 开始的连通陆地块,并将其标记为已访问。
同时更新该连通块的面积。
"""
global count # 使用全局变量 count 记录连通陆地的面积
q = deque() # 使用双端队列 deque 实现广度优先搜索
q.append((r, c)) # 将起始点 (r, c) 加入队列
g[r][c] = 0 # 将该点标记为已访问(即将其值设为 0)
count += 1 # 初始块计为 1(因为起始点也是一部分)
# 开始 BFS 遍历
while q:
r, c = q.popleft() # 从队列中取出当前点
# 遍历当前点的四个相邻方向
for di in directions:
next_r = r + di[0]
next_c = c + di[1]
# 判断是否越界,如果越界则跳过该方向
if next_c < 0 or next_c >= m or next_r < 0 or next_r >= n:
continue
# 如果相邻点是陆地(值为 1),则继续遍历
if g[next_r][next_c] == 1:
q.append((next_r, next_c)) # 将该点加入队列
g[next_r][next_c] = 0 # 标记为已访问
count += 1 # 每找到一块新陆地,面积加 1
# 遍历网格的边缘,将边缘的所有连通陆地块使用 BFS 标记
for i in range(n):
# 检查每行的第一个和最后一个元素(边缘列)
if g[i][0] == 1:
bfs(i, 0) # 如果边缘是陆地,调用 BFS 清除连通块
if g[i][m - 1] == 1:
bfs(i, m - 1) # 检查最后一列
for i in range(m):
# 检查第一行和最后一行的所有元素(边缘行)
if g[0][i] == 1:
bfs(0, i) # 如果边缘是陆地,调用 BFS 清除连通块
if g[n - 1][i] == 1:
bfs(n - 1, i) # 检查最后一行
# 经过边缘清理后,重新遍历网格,寻找所有剩余的非边缘陆地块
count = 0 # 重置 count,准备记录剩余连通陆地的面积
for i in range(n):
for j in range(m):
# 如果找到一块未访问的陆地,使用 BFS 计算其连通区域的面积
if g[i][j] == 1:
bfs(i, j)
# 输出最后计算的最大连通陆地的面积
print(count)
二、卡码102. 沉没孤岛
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域,且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。
现在你需要将所有孤岛“沉没”,即将孤岛中的所有陆地单元格(1)转变为水域单元格(0)。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。
之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出将孤岛“沉没”之后的岛屿矩阵。 注意:每个元素后面都有一个空格
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
1 1 0 0 0
1 1 0 0 0
0 0 0 0 0
0 0 0 1 1
提示信息
将孤岛沉没。
步骤一:深搜或者广搜将地图周边的 1 (陆地)全部改成 2 (特殊标记)
步骤二:将水域中间 1 (陆地)全部改成 水域(0)
步骤三:将之前标记的 2 改为 1 (陆地)
如图:
代码实现
from collections import deque
# 使用BFS将所有与边界相连的1标记为2
def bfs(grid, x, y, n, m):
q = deque()
q.append((x, y))
grid[x][y] = 2 # 将边界相连的1标记为2,表示不会沉没
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] # 四个方向:右、下、左、上
while q:
cur_x, cur_y = q.popleft()
for dx, dy in directions:
nx, ny = cur_x + dx, cur_y + dy
if 0 <= nx < n and 0 <= ny < m and grid[nx][ny] == 1:
grid[nx][ny] = 2 # 标记相连的1为2
q.append((nx, ny))
# 处理输入
n, m = map(int, input().split()) # 输入矩阵的行数n和列数m
grid = [list(map(int, input().split())) for _ in range(n)] # 输入矩阵
# 第一步:从边界出发,使用BFS将所有与边界相连的1标记为2
for i in range(n):
if grid[i][0] == 1:
bfs(grid, i, 0, n, m) # 左边界
if grid[i][m-1] == 1:
bfs(grid, i, m-1, n, m) # 右边界
for j in range(m):
if grid[0][j] == 1:
bfs(grid, 0, j, n, m) # 上边界
if grid[n-1][j] == 1:
bfs(grid, n-1, j, n, m) # 下边界
# 第二步:将孤岛中的1变为0,将之前标记的2恢复为1
for i in range(n):
for j in range(m):
if grid[i][j] == 1:
grid[i][j] = 0 # 沉没孤岛
elif grid[i][j] == 2:
grid[i][j] = 1 # 恢复与边界相连的陆地
# 第三步:输出处理后的矩阵
for row in grid:
print(" ".join(map(str, row))) # 每个元素后面都有一个空格
三、卡码103. 水流问题
题目描述
现有一个 N × M 的矩阵,每个单元格包含一个数值,这个数值代表该位置的相对高度。矩阵的左边界和上边界被认为是第一组边界,而矩阵的右边界和下边界被视为第二组边界。
矩阵模拟了一个地形,当雨水落在上面时,水会根据地形的倾斜向低处流动,但只能从较高或等高的地点流向较低或等高并且相邻(上下左右方向)的地点。我们的目标是确定那些单元格,从这些单元格出发的水可以达到第一组边界和第二组边界。
输入描述
第一行包含两个整数 N 和 M,分别表示矩阵的行数和列数。
后续 N 行,每行包含 M 个整数,表示矩阵中的每个单元格的高度。
输出描述
输出共有多行,每行输出两个整数,用一个空格隔开,表示可达第一组边界和第二组边界的单元格的坐标,输出顺序任意。
输入示例
5 5
1 3 1 2 4
1 2 1 3 2
2 4 7 2 1
4 5 6 1 1
1 4 1 2 1
输出示例
0 4
1 3
2 2
3 0
3 1
3 2
4 0
4 1
提示信息
图中的蓝色方块上的雨水既能流向第一组边界,也能流向第二组边界。所以最终答案为所有蓝色方块的坐标。
从第一组边界上的节点 逆流而上,将遍历过的节点都标记上。
同样从第二组边界的边上节点 逆流而上,将遍历过的节点也标记上。
然后两方都标记过的节点就是既可以流太平洋也可以流大西洋的节点。
从第一组边界边上节点出发,如图:
从第二组边界上节点出发,如图:
代码实现
# 全局集合 first 和 second,分别存储从第一边界和第二边界可到达的格子
first = set() # 可到达左边界或上边界的格子
second = set() # 可到达右边界或下边界的格子
# 定义四个方向:上、右、下、左
directions = [[-1, 0], [0, 1], [1, 0], [0, -1]] # 用于 DFS 访问相邻的格子
def dfs(i, j, graph, visited, side):
"""
使用深度优先搜索 (DFS) 遍历从 (i, j) 开始的格子,找到所有可以流向当前边界的格子。
:param i: 当前格子的行索引
:param j: 当前格子的列索引
:param graph: 二维矩阵,表示地形
:param visited: 访问标记矩阵,防止重复访问
:param side: 存储可以到达某一边界的格子集合
"""
# 如果当前格子已经被访问过,则返回
if visited[i][j]:
return
# 标记当前格子为已访问
visited[i][j] = True
# 将当前格子坐标添加到集合 side 中
side.add((i, j))
# 遍历当前格子的四个方向
for x, y in directions:
new_x = i + x # 相邻格子的行坐标
new_y = j + y # 相邻格子的列坐标
# 判断是否越界,并且判断相邻格子的值是否大于等于当前格子的值(保证水流可以流动)
if (
0 <= new_x < len(graph) # 确保行坐标不越界
and 0 <= new_y < len(graph[0]) # 确保列坐标不越界
and int(graph[new_x][new_y]) >= int(graph[i][j]) # 确保水可以从当前格子流到相邻格子
):
# 递归访问相邻格子
dfs(new_x, new_y, graph, visited, side)
def main():
global first # 全局变量 first,表示可到达第一边界的格子集合
global second # 全局变量 second,表示可到达第二边界的格子集合
# 输入矩阵的行数 N 和列数 M
N, M = map(int, input().strip().split())
graph = [] # 用于存储输入的矩阵
for _ in range(N):
row = input().strip().split() # 读取矩阵的每一行,并将其转换为列表
graph.append(row) # 将每一行添加到矩阵 graph 中
# 处理第一边界(上边界和左边界)
visited = [[False] * M for _ in range(N)] # 初始化访问标记矩阵 visited,所有格子初始时都未访问
for i in range(M):
dfs(0, i, graph, visited, first) # 从上边界的每个格子开始 DFS
for i in range(N):
dfs(i, 0, graph, visited, first) # 从左边界的每个格子开始 DFS
# 处理第二边界(下边界和右边界)
visited = [[False] * M for _ in range(N)] # 重新初始化访问标记矩阵 visited
for i in range(M):
dfs(N - 1, i, graph, visited, second) # 从下边界的每个格子开始 DFS
for i in range(N):
dfs(i, M - 1, graph, visited, second) # 从右边界的每个格子开始 DFS
# 计算可同时到达第一边界和第二边界的格子(取交集)
res = first & second # 交集运算
# 输出可到达第一边界和第二边界的格子坐标
for x, y in res:
print(f"{x} {y}") # 格式化输出坐标,使用空格分隔
if __name__ == "__main__":
main() # 程序入口,执行 main 函数
四、卡码104. 建造最大岛屿
题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,你最多可以将矩阵中的一格水变为一块陆地,在执行了此操作之后,矩阵中最大的岛屿面积是多少。
岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设矩阵外均被水包围。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出一个整数,表示最大的岛屿面积。
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
6
提示信息
对于上面的案例,有两个位置可将 0 变成 1,使得岛屿的面积最大,即 6。
其实每次深搜遍历计算最大岛屿面积,我们都做了很多重复的工作。
只要用一次深搜把每个岛屿的面积记录下来就好。
第一步:一次遍历地图,得出各个岛屿的面积,并做编号记录。可以使用map记录,key为岛屿编号,value为岛屿面积
第二步:再遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积。
拿如下地图的岛屿情况来举例: (1为陆地)
第一步,则遍历题目,并将岛屿到编号和面积上的统计,过程如图所示:
其实大家可以仔细看一下代码,n * n这个方格地图中,每个节点我们就遍历一次,并不会重复遍历。
第二步过程如图所示:
也就是遍历每一个0的方格,并统计其相邻岛屿面积,最后取一个最大值。
代码实现
# 定义四个方向:上、右、下、左
directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]
def dfs(grid, x, y, mark, island_areas, n, m):
"""
深度优先搜索 (DFS),遍历整个岛屿,标记并计算岛屿的面积
:param grid: 二维网格,表示地形
:param x: 当前行坐标
:param y: 当前列坐标
:param mark: 当前岛屿的编号
:param island_areas: 字典,记录每个岛屿的编号和面积
:param n: 网格的行数
:param m: 网格的列数
:return: 返回岛屿的面积
"""
stack = [(x, y)]
grid[x][y] = mark # 标记当前岛屿
area = 0
while stack:
i, j = stack.pop()
area += 1 # 当前岛屿面积加一
# 遍历当前格子的四个方向
for direction in directions:
new_i = i + direction[0]
new_j = j + direction[1]
# 如果新位置在网格范围内且是未访问的陆地
if 0 <= new_i < n and 0 <= new_j < m and grid[new_i][new_j] == 1:
grid[new_i][new_j] = mark # 标记新的位置为当前岛屿编号
stack.append((new_i, new_j))
# 记录当前岛屿的面积
island_areas[mark] = area
return area
def largest_island(grid):
"""
主函数,找到通过将一个0转换为1后可能得到的最大岛屿面积
:param grid: 二维网格,表示地形
:return: 返回可能的最大岛屿面积
"""
n = len(grid) # 行数
m = len(grid[0]) # 列数
island_areas = {} # 用于记录每个岛屿编号和对应的面积
mark = 2 # 岛屿编号从2开始,因为1和0已经在地图中使用了
# 计算每个岛屿的面积,并给岛屿做编号
for i in range(n):
for j in range(m):
if grid[i][j] == 1: # 找到一个新的岛屿
dfs(grid, i, j, mark, island_areas, n, m)
mark += 1
# 如果整个地图都是陆地,返回其面积
max_area = max(island_areas.values(), default=0) # 当前岛屿的最大面积
if max_area == n * m:
return max_area
# 尝试将每一个0变为1,并计算其相邻岛屿的合并面积
for i in range(n):
for j in range(m):
if grid[i][j] == 0: # 遍历所有的0,尝试将其变为1
seen_islands = set() # 用于记录相邻的岛屿编号
current_area = 1 # 将0变为1后的初始面积
# 检查四个方向相邻的岛屿
for direction in directions:
new_i = i + direction[0]
new_j = j + direction[1]
if 0 <= new_i < n and 0 <= new_j < m and grid[new_i][new_j] > 1:
island_id = grid[new_i][new_j]
if island_id not in seen_islands: # 防止重复计算同一个岛屿
seen_islands.add(island_id)
current_area += island_areas[island_id] # 合并面积
# 更新最大面积
max_area = max(max_area, current_area)
return max_area
# 处理输入
def main():
n, m = map(int, input().strip().split())
grid = []
for _ in range(n):
grid.append(list(map(int, input().strip().split())))
result = largest_island(grid)
print(result)
if __name__ == "__main__":
main()