算法工程师第四十五天(101.孤岛的总面积 沉没孤岛 103.水流问题 104.建造最大岛屿)

参考文献 代码随想录

一、孤岛的总面积

题目描述

给定一个由 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,此时地图为这样:

然后我们再去遍历这个地图,遇到有陆地的地方,去采用深搜或者广搜,边统计所有陆地。

广搜:

n, m = map(int, input().split())

grid = []  # 地图
for i in range(n):
    grid.append(list(map(int, input().split())))

direction = [[0,1],[0,-1],[1,0],[-1,0]] # 每点的方向
# 孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。
count = 0
def dfs(x, y):
    global count
    grid[x][y] = 0
    for i, j in direction:
        nex_x = x + i
        nex_y = y + j
        if nex_y < 0 or nex_x < 0 or nex_x >= len(grid) or nex_y >= len(grid[0]):
            continue
        if grid[nex_x][nex_y] == 1:  # 如果没有被访问且是陆地
            grid[nex_x][nex_y] = 0
            count += 1
            dfs(nex_x, nex_y)
            
for i in range(n):
    if grid[i][0] == 1: dfs(i, 0)
    if grid[i][m-1] == 1: dfs(i, m-1)

for i in range(m):
    if grid[0][i] == 1: dfs(0, i)
    if grid[n-1][i] == 1: dfs(n-1, i)
    
count = 0
for i in range( n):
    for j in range(m):
        if grid[i][j] == 1:
            count += 1
            dfs(i, j)
            
print(count)

深搜:

n, m = map(int, input().split())
from collections import deque
grid = []  # 地图
for i in range(n):
    grid.append(list(map(int, input().split())))

direction = [[0,1],[0,-1],[1,0],[-1,0]] # 每点的方向
# 孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。
count = 0
def bfs(x, y):
    global count
    queen = deque()
    queen.append([x, y])
    grid[x][y] = 0  #
    count += 1
    while queen:
        cur_x, cur_y = queen.popleft()
        for i, j in direction:
            nex_x = cur_x + i
            nex_y = cur_y + j
            if nex_y < 0 or nex_x < 0 or nex_x >= len(grid) or nex_y >= len(grid[0]):
                continue
            if grid[nex_x][nex_y] == 1:  # 如果没有被访问且是陆地
                grid[nex_x][nex_y] = 0
                count += 1
                queen.append([nex_x, nex_y])

# 处理边缘陆地(陆地边水地)            
for i in range(n):  
    if grid[i][0] == 1: bfs(i, 0)
    if grid[i][m-1] == 1: bfs(i, m-1)

for i in range(m):
    if grid[0][i] == 1: bfs(0, i)
    if grid[n-1][i] == 1: bfs(n-1, i)
    
count = 0  # 重新赋值,因为上面有累加
for i in range( n):
    for j in range(m):
        if grid[i][j] == 1:  # 如果是陆地,则求出面积
            bfs(i, j)
            
print(count)

二、沉没孤岛

题目描述

给定一个由 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。

问题分析:

这道题目和0101.孤岛的总面积 (opens new window)正好反过来了,0101.孤岛的总面积 (opens new window)是求 地图中间的空格数,而本题是要把地图中间的 1 都改成 0 。

那么两题在思路上也是差不多的。

思路依然是从地图周边出发,将周边空格相邻的陆地都做上标记,然后在遍历一遍地图,遇到 陆地 且没做过标记的,那么都是地图中间的 陆地 ,全部改成水域就行。

有的录友可能想,我在定义一个 visited 二维数组,单独标记周边的陆地,然后遍历地图的时候同时对 数组board 和 数组visited 进行判断,决定 陆地是否变成水域。

这样做其实就有点麻烦了,不用额外定义空间了,标记周边的陆地,可以直接改陆地为其他特殊值作为标记。

步骤一:深搜或者广搜将地图周边的 1 (陆地)全部改成 2 (特殊标记)

步骤二:将水域中间 1 (陆地)全部改成 水域(0)

步骤三:将之前标记的 2 改为 1 (陆地)

如图:

深搜:

n, m = map(int, input().split())
from collections import deque
grid = []  # 地图
for i in range(n):
    grid.append(list(map(int, input().split())))

direction = [[0,1],[0,-1],[1,0],[-1,0]] # 每点的方向

def dfs(x, y):
    grid[x][y] = 2 
    for i, j in direction:
        nex_x = x + i
        nex_y = y + j
        if  nex_y < 0 or nex_x < 0 or nex_x >= len(grid) or nex_y >= len(grid[0]):
            continue
        if grid[nex_x][nex_y] == 0 or grid[nex_x][nex_y] == 2: 
            continue
        if grid[nex_x][nex_y] == 1:
            grid[nex_x][nex_y] = 2
            dfs(nex_x, nex_y)
            

# 处理边缘陆地(陆地边水地)            
for i in range(n):  # 现将边缘陆地标记成吉他的数字
    if grid[i][0] == 1: dfs(i, 0)
    if grid[i][m-1] == 1: dfs(i, m-1)

for i in range(m):
    if grid[0][i] == 1: dfs(0, i)
    if grid[n-1][i] == 1: dfs(n-1, i)

# 然后先把1变成水,然后在把周围的陆地换回来
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
for i in grid:
    print(' '.join(map(str, i)))

广搜:

n, m = map(int, input().split())
from collections import deque
grid = []  # 地图
for i in range(n):
    grid.append(list(map(int, input().split())))

direction = [[0,1],[0,-1],[1,0],[-1,0]] # 每点的方向

def bfs(x, y):
    queen = deque()
    queen.append([x, y])
    grid[x][y] = 2 #
    while queen:
        cur_x, cur_y = queen.popleft()
        for i, j in direction:
            nex_x = cur_x + i
            nex_y = cur_y + j
            if  nex_y < 0 or nex_x < 0 or nex_x >= len(grid) or nex_y >= len(grid[0]):
                continue
            if grid[nex_x][nex_y] == 0 or grid[nex_x][nex_y] == 2: 
                continue
            if grid[nex_x][nex_y] == 1:
                grid[nex_x][nex_y] = 2
                queen.append([nex_x, nex_y])
            

# 处理边缘陆地(陆地边水地)            
for i in range(n):  # 现将边缘陆地标记成吉他的数字
    if grid[i][0] == 1: bfs(i, 0)
    if grid[i][m-1] == 1: bfs(i, m-1)

for i in range(m):
    if grid[0][i] == 1: bfs(0, i)
    if grid[n-1][i] == 1: bfs(n-1, i)

# 然后先把1变成水,然后在把周围的陆地换回来
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
for i in grid:
    print(' '.join(map(str, i)))

三、水流问题

题目描述

现有一个 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 <= 100。

问题分析:

一个比较直白的想法,其实就是 遍历每个点,然后看这个点 能不能同时到达第一组边界和第二组边界。

至于遍历方式,可以用dfs,也可以用bfs,以下用dfs来举例。

那么这种思路的实现代码如下:

#include <iostream>
#include <vector>
using namespace std;
int n, m;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1};

// 从 x,y 出发 把可以走的地方都标记上
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) {
    if (visited[x][y]) return;

    visited[x][y] = true;

    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue;
        if (grid[x][y] < grid[nextx][nexty]) continue; // 高度不合适

        dfs (grid, visited, nextx, nexty);
    }
    return;
}
bool isResult(vector<vector<int>>& grid, int x, int y) {
    vector<vector<bool>> visited(n, vector<bool>(m, false));

    // 深搜,将x,y出发 能到的节点都标记上。
    dfs(grid, visited, x, y);
    bool isFirst = false;
    bool isSecond = false;

    // 以下就是判断x,y出发,是否到达第一组边界和第二组边界
    // 第一边界的上边
    for (int j = 0; j < m; j++) {
        if (visited[0][j]) {
            isFirst = true;
            break;
        }
    }
    // 第一边界的左边
    for (int i = 0; i < n; i++) {
        if (visited[i][0]) {
            isFirst = true;
            break;
        }
    }
    // 第二边界右边
    for (int j = 0; j < m; j++) {
        if (visited[n - 1][j]) {
            isSecond = true;
            break;
        }
    }
    // 第二边界下边
    for (int i = 0; i < n; i++) {
        if (visited[i][m - 1]) {
            isSecond = true;
            break;
        }
    }
    if (isFirst && isSecond) return true;
    return false;
}


int main() {
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }
    // 遍历每一个点,看是否能同时到达第一组边界和第二组边界
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (isResult(grid, i, j)) {
                cout << i << " " << j << endl;
            }
        }
    }
}

这种思路很直白,但很明显,以上代码超时了。 来看看时间复杂度。

遍历每一个节点,是 m * n,遍历每一个节点的时候,都要做深搜,深搜的时间复杂度是: m * n

那么整体时间复杂度 就是 O(m^2 * n^2) ,这是一个四次方的时间复杂度。

#优化

那么我们可以 反过来想,从第一组边界上的节点 逆流而上,将遍历过的节点都标记上。

同样从第二组边界的边上节点 逆流而上,将遍历过的节点也标记上。

然后两方都标记过的节点就是既可以流太平洋也可以流大西洋的节点

从第一组边界边上节点出发,如图:

从第二组边界上节点出发,如图:

按照这样的逻辑,就可以写出如下遍历代码:(详细注释)

#include <iostream>
#include <vector>
using namespace std;
int n, m;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1};
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) {
    if (visited[x][y]) return;

    visited[x][y] = true;

    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m) continue;
        if (grid[x][y] > grid[nextx][nexty]) continue; // 注意:这里是从低向高遍历

        dfs (grid, visited, nextx, nexty);
    }
    return;
}



int main() {

    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }
    // 标记从第一组边界上的节点出发,可以遍历的节点
    vector<vector<bool>> firstBorder(n, vector<bool>(m, false));

    // 标记从第一组边界上的节点出发,可以遍历的节点
    vector<vector<bool>> secondBorder(n, vector<bool>(m, false));

    // 从最上和最下行的节点出发,向高处遍历
    for (int i = 0; i < n; i++) {
        dfs (grid, firstBorder, i, 0); // 遍历最左列,接触第一组边界
        dfs (grid, secondBorder, i, m - 1); // 遍历最右列,接触第二组边界
    }

    // 从最左和最右列的节点出发,向高处遍历
    for (int j = 0; j < m; j++) {
        dfs (grid, firstBorder, 0, j); // 遍历最上行,接触第一组边界
        dfs (grid, secondBorder, n - 1, j); // 遍历最下行,接触第二组边界
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            // 如果这个节点,从第一组边界和第二组边界出发都遍历过,就是结果
            if (firstBorder[i][j] && secondBorder[i][j]) cout << i << " " << j << endl;;
        }
    }


}

时间复杂度分析, 关于dfs函数搜索的过程 时间复杂度是 O(n * m),这个大家比较容易想。

关键看主函数,那么每次dfs的时候,上面还是有for循环的。

第一个for循环,时间复杂度是:n * (n * m) 。

第二个for循环,时间复杂度是:m * (n * m)。

所以本题看起来 时间复杂度好像是 : n * (n * m) + m * (n * m) = (m * n) * (m + n) 。

其实这是一个误区,大家再自己看 dfs函数的实现,其实 有visited函数记录 走过的节点,而走过的节点是不会再走第二次的。

所以 调用dfs函数,只要参数传入的是 数组 firstBorder,那么地图中 每一个节点其实就遍历一次,无论你调用多少次

同理,调用dfs函数,只要 参数传入的是 数组 secondBorder,地图中每个节点也只会遍历一次。

所以,以下这段代码的时间复杂度是 2 * n * m。 地图用每个节点就遍历了两次,参数传入 firstBorder 的时候遍历一次,参数传入 secondBorder 的时候遍历一次。

// 从最上和最下行的节点出发,向高处遍历
for (int i = 0; i < n; i++) {
    dfs (grid, firstBorder, i, 0); // 遍历最左列,接触第一组边界
    dfs (grid, secondBorder, i, m - 1); // 遍历最右列,接触第二组边界
}

// 从最左和最右列的节点出发,向高处遍历
for (int j = 0; j < m; j++) {
    dfs (grid, firstBorder, 0, j); // 遍历最上行,接触第一组边界
    dfs (grid, secondBorder, n - 1, j); // 遍历最下行,接触第二组边界
}

那么本题整体的时间复杂度其实是: 2 * n * m + n * m ,所以最终时间复杂度为 O(n * m) 。

空间复杂度为:O(n * m) 这个就不难理解了。开了几个 n * m 的数组。

深搜:

first = set()  # 找到图中可以到达第一组边界的
second = set() # 找到图中可以到达第二组边界的,为什么要用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()  # 找到图中可以到达第二组边界的,为什么要用set因为避免重复
directions = [[-1, 0], [0, 1], [1, 0], [0, -1]]


def bfs(i, j, graph, visited, side):
    if visited[i][j]:  # 遍历过的就不用寻找了
        return
    visited[i][j] = True
    side.add((i, j))  # 为什么第一个直接记录,因为它们本身就和边间相邻
    from collections import deque
    q = deque()
    q.append([i, j])
    while q:
        cur_x, cur_y = q.popleft()
        for x, y in directions:
            new_x = cur_x + x
            new_y = cur_y + y
            if 0 <= new_x < len(graph) and 0 <= new_y < len(graph[0]) and int(graph[new_x][new_y]) >= int(graph[cur_x][cur_y]) and not visited[new_x][new_y]:  # 如果坐标轴没有越界,并且下一个的高度要大于当前的高度那么,就可以遍历下一个高度
                q.append([new_x, new_y])
                side.add((new_x, new_y))
                visited[new_x][new_y] = True


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):
        bfs(0, i, graph, visited, first)
    for i in range(N):
        bfs(i, 0, graph, visited, first)

    # 是否可到达第二边界
    visited = [[False] * M for _ in range(N)]
    for i in range(M):
        bfs(N - 1, i, graph, visited, second)
    for i in range(N):
        bfs(i, M - 1, graph, visited, second)

    # 可到达第一边界和第二边界
    res = first & second
    for x, y in res:
        print(f"{x} {y}")


if __name__ == "__main__":
    main()

四、建造最大岛屿

题目描述

给定一个由 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为陆地)

第一步,则遍历题目,并将岛屿到编号和面积上的统计,过程如图所示:

本过程代码如下:

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y, int mark) {
    if (visited[x][y] || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水
    visited[x][y] = true; // 标记访问过
    grid[x][y] = mark; // 给陆地标记新标签
    count++;
    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳过
        dfs(grid, visited, nextx, nexty, mark);
    }
}

int largestIsland(vector<vector<int>>& grid) {
    int n = grid.size(), m = grid[0].size();
    vector<vector<bool>> visited = vector<vector<bool>>(n, vector<bool>(m, false)); // 标记访问过的点
    unordered_map<int ,int> gridNum;
    int mark = 2; // 记录每个岛屿的编号
    bool isAllGrid = true; // 标记是否整个地图都是陆地
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 0) isAllGrid = false;
            if (!visited[i][j] && grid[i][j] == 1) {
                count = 0;
                dfs(grid, visited, i, j, mark); // 将与其链接的陆地都标记上 true
                gridNum[mark] = count; // 记录每一个岛屿的面积
                mark++; // 记录下一个岛屿编号
            }
        }
    }
}

这个过程时间复杂度 n * n 。可能有录友想:分明是两个for循环下面套这一个dfs,时间复杂度怎么回事 n * n呢?

其实大家可以仔细看一下代码,n * n这个方格地图中,每个节点我们就遍历一次,并不会重复遍历

第二步过程如图所示:

也就是遍历每一个0的方格,并统计其相邻岛屿面积,最后取一个最大值。

这个过程的时间复杂度也为 n * n。

所以整个解法的时间复杂度,为 n * n + n * n 也就是 n^2。

当然这里还有一个优化的点,就是 可以不用 visited数组,因为有mark来标记,所以遍历过的grid[i][j]是不等于1的。

深搜:

def dfs(grid, visited, x, y, mark): # 搜索并标记和求面积
    if visited[x][y] or grid[x][y] == 0:  # 如果访问过并且额为海洋时,就继续
        return 0
    visited[x][y] = True  # 然后级标记
    grid[x][y] = mark  # 然后标记上
    count = 1  # 统计面积
    for dx, dy in [(0, 1), (1, 0), (-1, 0), (0, -1)]:
        nextx, nexty = x + dx, y + dy
        if 0 <= nextx < len(grid) and 0 <= nexty < len(grid[0]):
            count += dfs(grid, visited, nextx, nexty, mark)
    return count

def max_island_area_after_addition(grid):
    n, m = len(grid), len(grid[0])
    visited = [[False] * m for _ in range(n)]
    gridNum = {}  # 记录每个标号对应的面积,key为编号,value为面积
    mark = 2  # 为什么标记为2 因为为了不重复
    isAllGrid = True  # 还有一种情况,就是全部都为陆地
    
    for i in range(n):
        for j in range(m):
            if grid[i][j] == 0:  # 如果为海洋,那么就不可能全部为陆地
                isAllGrid = False
            if not visited[i][j] and grid[i][j] == 1:  # 如果没有被访问过并且是陆地
                count = dfs(grid, visited, i, j, mark)
                gridNum[mark] = count  # gridNum 记录的是每个标号对应的面积
                mark += 1  # 到下次的时候,就要加1,避免重复
    
    if isAllGrid:  # 如果全是陆地你,那么直接输出面积
        return n * m
    
    result = 0  # 统计相连与不相连的最大结果
    for i in range(n):
        for j in range(m):
            if grid[i][j] == 0:  # 只能中海洋出发,然后变成陆地,最后求最大值
                count = 1  # 因为只能等于1
                visitedGrid = set()   # 这个是记录从当前点向四周扩散后避免重复计算
                for dx, dy in [(0, 1), (1, 0), (-1, 0), (0, -1)]:
                    neari, nearj = i + dx, j + dy  # 找出当前海洋的下一个节点的编号,
                    if 0 <= neari < n and 0 <= nearj < m:
                        island_id = grid[neari][nearj]  # 并且不为海洋,还没有访问过
                        if island_id != 0 and island_id not in visitedGrid:
                            count += gridNum.get(island_id, 0) 
                            visitedGrid.add(island_id)
                result = max(result, count)
    
    return result

# 读取输入
n, m = map(int, input().split())
grid = [list(map(int, input().split())) for _ in range(n)]

# 输出结果
print(max_island_area_after_addition(grid))

广搜:

from collections import deque

def bfs(grid, visited, x, y, mark):
    directions = [(0, 1), (1, 0), (-1, 0), (0, -1)]
    queue = deque([(x, y)])
    count = 0
    while queue:
        cx, cy = queue.popleft()
        if visited[cx][cy] or grid[cx][cy] == 0:
            continue
        visited[cx][cy] = True
        grid[cx][cy] = mark
        count += 1
        for dx, dy in directions:
            nextx, nexty = cx + dx, cy + dy
            if 0 <= nextx < len(grid) and 0 <= nexty < len(grid[0]) and not visited[nextx][nexty] and grid[nextx][nexty] == 1:
                queue.append((nextx, nexty))
    return count

def max_island_area_after_addition(grid):
    n, m = len(grid), len(grid[0])
    visited = [[False] * m for _ in range(n)]
    gridNum = {}
    mark = 2
    isAllGrid = True
    
    for i in range(n):
        for j in range(m):
            if grid[i][j] == 0:
                isAllGrid = False
            if not visited[i][j] and grid[i][j] == 1:
                count = bfs(grid, visited, i, j, mark)
                gridNum[mark] = count
                mark += 1
    
    if isAllGrid:
        return n * m
    
    result = 0
    for i in range(n):
        for j in range(m):
            if grid[i][j] == 0:
                count = 1
                visitedGrid = set()
                for dx, dy in [(0, 1), (1, 0), (-1, 0), (0, -1)]:
                    neari, nearj = i + dx, j + dy
                    if 0 <= neari < n and 0 <= nearj < m:
                        island_id = grid[neari][nearj]
                        if island_id != 0 and island_id not in visitedGrid:
                            count += gridNum.get(island_id, 0)
                            visitedGrid.add(island_id)
                result = max(result, count)
    
    return result

# 读取输入
n, m = map(int, input().split())
grid = [list(map(int, input().split())) for _ in range(n)]

# 输出结果
print(max_island_area_after_addition(grid))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值