目录
101.孤岛的总面积
- 题目链接:卡码网: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。
数据范围:
1 <= M, N <= 50。
思路
本题使用dfs,bfs,并查集都是可以的。
本题要求找到不靠边的陆地面积,那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图 统计此时还剩下的陆地就可以了。
如图,在遍历地图周围四个边,靠地图四边的陆地,都为绿色,
在遇到地图周边陆地的时候,将1都变为0,此时地图为这样:
然后我们再去遍历这个地图,遇到有陆地的地方,去采用深搜或者广搜,边统计所有陆地。
方法一: 深搜
direction = [[0,1],[1,0],[0,-1],[-1,0]]
count = 0
def dfs(grid,x,y):
global count
grid[x][y] = 0
for i,j in direction:
next_x = x + i
next_y = y + j
if 0 <= next_x < len(grid) and 0 <= next_y < len(grid[0]):
if grid[next_x][next_y] == 1 :
grid[next_x][next_y] = 0
count += 1
dfs(grid,next_x,next_y)
def main():
n,m = map(int,input().split())
grid = []
for i in range(n):
grid.append(list(map(int,input().split())))
for i in range(n):
if grid[i][0] == 1: dfs(grid,i,0)
if grid[i][m-1] == 1: dfs(grid,i,m-1)
for i in range(m):
if grid[0][i] == 1: dfs(grid,0,i)
if grid[n-1][i] == 1: dfs(grid,n-1,i)
global count
count = 0
for i in range(n):
for j in range(m):
if grid[i][j] == 1:
count += 1
dfs(grid,i,j)
print(count)
if __name__ == "__main__":
main()
方法二:广搜
direction = [[0,1],[1,0],[0,-1],[-1,0]]
count = 0
from collections import deque
def bfs(grid,x,y):
que = deque()
que.append((x,y))
global count
grid[x][y] = 0
while que:
cur_x ,cur_y = que.popleft()
for i ,j in direction:
next_x = cur_x + i
next_y = cur_y + j
if 0 <= next_x < len(grid) and 0 <= next_y < len(grid[0]):
if grid[next_x][next_y] == 1 :
que.append((next_x,next_y))
grid[next_x][next_y] = 0
count += 1
def main():
n,m = map(int,input().split())
grid = []
for i in range(n):
grid.append(list(map(int,input().split())))
for i in range(n):
if grid[i][0] == 1: bfs(grid,i,0)
if grid[i][m-1] == 1: bfs(grid,i,m-1)
for i in range(m):
if grid[0][i] == 1: bfs(grid,0,i)
if grid[n-1][i] == 1: bfs(grid,n-1,i)
global count
count = 0
for i in range(n):
for j in range(m):
if grid[i][j] == 1:
count += 1
bfs(grid,i,j)
print(count)
if __name__ == "__main__":
main()
102.沉没孤岛
- 题目链接:卡码网题目链接(ACM模式)
文章讲解:代码随想录
题目描述:
给定一个由 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 <= M, N <= 50
思路
这道题目和101.孤岛的总面积 正好反过来了,101.孤岛的总面积 是求 地图中间的空格数,而本题是要把地图中间的 1 都改成 0 。
那么两题在思路上也是差不多的。
思路依然是从地图周边出发,将周边空格相邻的陆地都做上标记,然后在遍历一遍地图,遇到 陆地 且没做过标记的,那么都是地图中间的 陆地 ,全部改成水域就行。
有的录友可能想,我在定义一个 visited 二维数组,单独标记周边的陆地,然后遍历地图的时候同时对 数组board 和 数组visited 进行判断,决定 陆地是否变成水域。
这样做其实就有点麻烦了,不用额外定义空间了,标记周边的陆地,可以直接改陆地为其他特殊值作为标记。
步骤一:深搜或者广搜将地图周边的 1 (陆地)全部改成 2 (特殊标记)
步骤二:将水域中间 1 (陆地)全部改成 水域(0)
步骤三:将之前标记的 2 改为 1 (陆地)
如图:
方法一:深搜
direction = [[0,1],[1,0],[0,-1],[-1,0]]
def dfs(grid,x,y):
grid[x][y] = 2
for i,j in direction:
next_x = x + i
next_y = y + j
if 0 <= next_x < len(grid) and 0 <= next_y < len(grid[0]):
if grid[next_x][next_y] == 1 :
grid[next_x][next_y] = 2
dfs(grid,next_x,next_y)
def main():
n,m = map(int,input().split())
grid = []
for i in range(n):
grid.append(list(map(int,input().split())))
for i in range(n):
if grid[i][0] == 1: dfs(grid,i,0)
if grid[i][m-1] == 1: dfs(grid,i,m-1)
for i in range(m):
if grid[0][i] == 1: dfs(grid,0,i)
if grid[n-1][i] == 1: dfs(grid,n-1,i)
for i in range(n):
for j in range(m):
if grid[i][j] == 1: grid[i][j] = 0
if grid[i][j] == 2: grid[i][j] = 1
print(grid[i][j],end=" ")
print()
if __name__ == "__main__":
main()
方法二:广搜
direction = [[0,1],[1,0],[0,-1],[-1,0]]
from collections import deque
def bfs(grid,x,y):
que = deque()
que.append((x,y))
grid[x][y] = 2
while que:
cur_x ,cur_y = que.popleft()
for i ,j in direction:
next_x = cur_x + i
next_y = cur_y + j
if 0 <= next_x < len(grid) and 0 <= next_y < len(grid[0]):
if grid[next_x][next_y] == 1 :
que.append((next_x,next_y))
grid[next_x][next_y] = 2
def main():
n,m = map(int,input().split())
grid = []
for i in range(n):
grid.append(list(map(int,input().split())))
for i in range(n):
if grid[i][0] == 1: bfs(grid,i,0)
if grid[i][m-1] == 1: bfs(grid,i,m-1)
for i in range(m):
if grid[0][i] == 1: bfs(grid,0,i)
if grid[n-1][i] == 1: bfs(grid,n-1,i)
for i in range(n):
for j in range(m):
if grid[i][j] == 1: grid[i][j] = 0
if grid[i][j] == 2: grid[i][j] = 1
print(' '.join(map(str, grid[i])))
if __name__ == "__main__":
main()
103.水流问题
- 题目链接:卡码网题目链接(ACM模式)
文章讲解:代码随想录
题目描述:
现有一个 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
提示信息:
图中的蓝色方块上的雨水既能流向第一组边界,也能流向第二组边界。所以最终答案为所有蓝色方块的坐标。
数据范围:
1 <= M, N <= 50
思路
一个比较直白的想法,其实就是 遍历每个点,然后看这个点 能不能同时到达第一组边界和第二组边界。
至于遍历方式,可以用dfs,也可以用bfs,以下用dfs来举例。
这种思路很直白,但很明显,代码超时了。 来看看时间复杂度。
遍历每一个节点,是 m * n,遍历每一个节点的时候,都要做深搜,深搜的时间复杂度是: m * n
那么整体时间复杂度 就是 O(m^2 * n^2) ,这是一个四次方的时间复杂度。
优化
那么我们可以 反过来想,从第一组边界上的节点 逆流而上,将遍历过的节点都标记上。
同样从第二组边界的边上节点 逆流而上,将遍历过的节点也标记上。
然后两方都标记过的节点就是既可以流太平洋也可以流大西洋的节点。
从第一组边界边上节点出发,如图:
从第二组边界上节点出发,如图:
方法一:深搜
first = set()
second = set()
directions = [[-1, 0], [0, 1], [1, 0], [0, -1]]
def dfs(i, j, graph, visited, side):
if visited[i][j]:
return
visited[i][j] = True
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
global second
N, M = map(int, input().strip().split())
graph = []
for _ in range(N):
row = input().strip().split()
graph.append(row)
# 是否可到达第一边界
visited = [[False] * M for _ in range(N)]
for i in range(M):
dfs(0, i, graph, visited, first)
for i in range(N):
dfs(i, 0, graph, visited, first)
# 是否可到达第二边界
visited = [[False] * M for _ in range(N)]
for i in range(M):
dfs(N - 1, i, graph, visited, second)
for i in range(N):
dfs(i, M - 1, graph, visited, second)
# 可到达第一边界和第二边界
res = first & second
for x, y in res:
print(f"{x} {y}")
if __name__ == "__main__":
main()
方法二:广搜
first = set()
second = set()
direction = [[0,1],[1,0],[0,-1],[-1,0]]
from collections import deque
def bfs(grid,visited,x,y,side):
que = deque()
que.append((x,y))
side.add((x,y))
while que:
cur_x,cur_y = que.popleft()
for i,j in direction:
next_x = cur_x + i
next_y = cur_y + j
if 0 <= next_x < len(grid) and 0 <= next_y < len(grid[0]) :
if grid[next_x][next_y] >= grid[x][y] and not visited[next_x][next_y]:
que.append((next_x,next_y))
side.add((next_x,next_y))
visited[next_x][next_y] = True
def main():
global first
global second
n,m = map(int,input().split())
grid = []
for i in range(n):
grid.append(list(map(int,input().split())))
# 第一组边界
visited = [[False] * m for _ in range(n)]
for i in range(n):
visited[i][0] = True
bfs(grid,visited,i,0,first)
for i in range(m):
visited[0][i] = True
bfs(grid,visited,0,i,first)
# 第二组边界
visited = [[False] * m for _ in range(n)]
for i in range(n):
visited[i][m-1] = True
bfs(grid,visited,i,m-1,second)
for i in range(m):
visited[n-1][i] = True
bfs(grid,visited,n-1,i,second)
res = first & second
for x,y in res:
print(f'{x} {y}')
if __name__ == "__main__":
main()
104.建造最大岛屿
- 题目链接:卡码网题目链接(ACM模式)
文章讲解:代码随想录
题目描述:
给定一个由 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。
数据范围:
1 <= M, N <= 50。
思路
本题的一个暴力想法,应该是遍历地图尝试 将每一个 0 改成1,然后去搜索地图中的最大的岛屿面积。
计算地图的最大面积:遍历地图 + 深搜岛屿,时间复杂度为 n * n。
(其实使用深搜还是广搜都是可以的,其目的就是遍历岛屿做一个标记,相当于染色,那么使用哪个遍历方式都行,以下我用深搜来讲解)
每改变一个0的方格,都需要重新计算一个地图的最大面积,所以 整体时间复杂度为:n^4。
优化思路
其实每次深搜遍历计算最大岛屿面积,我们都做了很多重复的工作。
只要用一次深搜把每个岛屿的面积记录下来就好。
第一步:一次遍历地图,得出各个岛屿的面积,并做编号记录。可以使用map记录,key为岛屿编号,value为岛屿面积
第二步:再遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积。
拿如下地图的岛屿情况来举例: (1为陆地)
第一步,则遍历题目,并将岛屿到编号和面积上的统计,过程如图所示:
方法一:深搜
from typing import List
from collections import defaultdict
class Solution:
def __init__(self):
self.direction = [(1,0),(-1,0),(0,1),(0,-1)]
self.res = 0
# self.count = 0
# 标记岛屿编号
self.idx = 1
# 记录每个岛屿面积
self.count_area = defaultdict(int)
def max_area_island(self, grid: List[List[int]]) -> int:
if not grid or len(grid) == 0 or len(grid[0]) == 0:
return 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == 1:
# self.count = 0
self.idx += 1
self.dfs(grid,i,j)
# print(grid)
self.check_area(grid)
# print(self.count_area)
if self.check_largest_connect_island(grid=grid):
return self.res + 1
return max(self.count_area.values())
# 深度优先搜索
def dfs(self,grid,row,col):
grid[row][col] = self.idx
# self.count += 1
for dr,dc in self.direction:
_row = dr + row
_col = dc + col
if 0<=_row<len(grid) and 0<=_col<len(grid[0]) and grid[_row][_col] == 1:
self.dfs(grid,_row,_col)
return
# 计算不同岛屿的面积
def check_area(self,grid):
m, n = len(grid), len(grid[0])
for row in range(m):
for col in range(n):
self.count_area[grid[row][col]] = self.count_area.get(grid[row][col],0) + 1
return
# 计算每个空格0变为1后,所能形成的最大岛屿面积
def check_largest_connect_island(self,grid):
m, n = len(grid), len(grid[0])
has_connect = False
for row in range(m):
for col in range(n):
if grid[row][col] == 0:
has_connect = True
area = 0
visited = set()
for dr, dc in self.direction:
_row = row + dr
_col = col + dc
if 0<=_row<len(grid) and 0<=_col<len(grid[0]) and grid[_row][_col] != 0 and grid[_row][_col] not in visited:
visited.add(grid[_row][_col])
area += self.count_area[grid[_row][_col]]
self.res = max(self.res, area)
return has_connect
def main():
m, n = map(int, input().split())
grid = []
for i in range(m):
grid.append(list(map(int,input().split())))
sol = Solution()
print(sol.max_area_island(grid))
if __name__ == '__main__':
main()
方法二:深搜
import collections
directions = [[-1, 0], [0, 1], [0, -1], [1, 0]]
area = 0
def dfs(i, j, grid, visited, num):
global area
if visited[i][j]:
return
visited[i][j] = True
grid[i][j] = num # 标记岛屿号码
area += 1
for x, y in directions:
new_x = i + x
new_y = j + y
if (
0 <= new_x < len(grid)
and 0 <= new_y < len(grid[0])
and grid[new_x][new_y] == "1"
):
dfs(new_x, new_y, grid, visited, num)
def main():
global area
N, M = map(int, input().strip().split())
grid = []
for i in range(N):
grid.append(input().strip().split())
visited = [[False] * M for _ in range(N)]
rec = collections.defaultdict(int)
cnt = 2
for i in range(N):
for j in range(M):
if grid[i][j] == "1":
area = 0
dfs(i, j, grid, visited, cnt)
rec[cnt] = area # 纪录岛屿面积
cnt += 1
res = 0
for i in range(N):
for j in range(M):
if grid[i][j] == "0":
max_island = 1 # 将水变为陆地,故从1开始计数
v = set()
for x, y in directions:
new_x = i + x
new_y = j + y
if (
0 <= new_x < len(grid)
and 0 <= new_y < len(grid[0])
and grid[new_x][new_y] != "0"
and grid[new_x][new_y] not in v # 岛屿不可重复
):
max_island += rec[grid[new_x][new_y]]
v.add(grid[new_x][new_y])
res = max(res, max_island)
if res == 0:
return max(rec.values()) # 无水的情况
return res
if __name__ == "__main__":
print(main())