Leetcode刷题笔记64:图论4(417. 太平洋大西洋水流问题-827. 最大人工岛)

116 阅读4分钟

导语

leetcode刷题笔记记录,主要记录题目包括:

Leetcode 417. 太平洋大西洋水流问题

题目描述

有一个 m × n 的矩形岛屿,与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而  “大西洋”  处于大陆的右边界和下边界。

这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights , heights[r][c] 表示坐标 (r, c) 上单元格 高于海平面的高度 。

岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。

返回网格坐标 result 的 2D 列表 ,其中 result[i] = [ri, ci] 表示雨水从单元格 (ri, ci) 流动 既可流向太平洋也可流向大西洋 。

 

示例 1:

输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]

示例 2:

输入: heights = [[2,1],[1,2]]
输出: [[0,0],[0,1],[1,0],[1,1]]

 

提示:

  • m == heights.length
  • n == heights[r].length
  • 1 <= m, n <= 200
  • 0 <= heights[r][c] <= 105

解法

乍一看这道题目没什么思路,如果使用暴力法求解,那么复杂度将会是O(N4)O(N^4),不可取。参考代码随想录,这里反向进行思考,即

从太平洋边上的节点 逆流而上,将遍历过的节点都标记上。 从大西洋的边上节点 逆流而长,将遍历过的节点也标记上。 然后两方都标记过的节点就是既可以流太平洋也可以流大西洋的节点。

这是使用dfs进行递归,定义两个二维数组,分别用于记录太平洋和大西洋能流到的单元格

        pacific = [[False]*n for _ in range(m)]
        atlantic = [[False]*n for _ in range(m)]

之后,判断二者都为True的格子即可。

from typing import List

class Solution:
    def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
        # 初始化结果列表
        result = []
        
        # 获取矩阵的行数和列数
        m, n = len(heights), len(heights[0])
        
        # 初始化方向数组,表示上下左右四个方向
        directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
        
        # 初始化两个二维数组,分别用于记录太平洋和大西洋能流到的单元格
        pacific = [[False]*n for _ in range(m)]
        atlantic = [[False]*n for _ in range(m)]

        # 深度优先搜索函数
        def dfs(visited, x, y):
            # 如果该点已被访问,直接返回
            if visited[x][y]:
                return
            
            # 标记该点为已访问
            visited[x][y] = True
            
            # 遍历上下左右四个方向
            for dx, dy in directions:
                new_x, new_y = x+dx, y+dy
                
                # 判断新坐标是否在矩阵范围内,并且是否满足流动条件(高度)
                if 0<=new_x<m and 0<=new_y<n and heights[x][y]<=heights[new_x][new_y]:
                    dfs(visited, new_x, new_y)

        # 对太平洋和大西洋边界进行深度优先搜索
        for i in range(m):
            dfs(pacific, i, 0)
            dfs(atlantic, i, n-1)
        for j in range(n):
            dfs(pacific, 0, j)
            dfs(atlantic, m-1, j)

        # 遍历整个矩阵,找到既可以流到太平洋也可以流到大西洋的单元格
        for i in range(m):
            for j in range(n):
                if pacific[i][j] and atlantic[i][j]:
                    result.append([i, j])

        return result

Leetcode 827. 最大人工岛

题目描述

给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。

返回执行此操作后,grid 中最大的岛屿面积是多少?

岛屿 由一组上、下、左、右四个方向相连的 1 形成。

 

示例 1:

输入: grid = [[1, 0], [0, 1]]
输出: 3
解释: 将一格0变成1,最终连通两个小岛得到面积为 3 的岛屿。

示例 2:

输入: grid = [[1, 1], [1, 0]]
输出: 4
解释: 将一格0变成1,岛屿的面积扩大为 4

示例 3:

输入: grid = [[1, 1], [1, 1]]
输出: 4
解释: 没有0可以让我们变成1,面积依然为 4

 

提示:

  • n == grid.length
  • n == grid[i].length
  • 1 <= n <= 500
  • grid[i][j] 为 0 或 1

解法

这是一道Hard的题目,整体的思路如下:

  • 递归遍历每个岛屿,修改标记,统计面积
  • 记录最大岛屿面积(这里是为了防止增加1后也没有比最大的岛屿面积大的情况)
  • 遍历海洋,设置人工岛,访问人工岛上下左右四个位置是否有岛屿,有的话进行面积叠加,并更新最大岛屿面积
  • 返回最大岛屿面积(注意:人工岛的面积要再加一)

完整代码如下:

from collections import defaultdict
from typing import List  # 不要忘记导入 List 类型

class Solution:
    def largestIsland(self, grid: List[List[int]]) -> int:
        # 获取网格的行数和列数
        m, n = len(grid), len(grid[0])
        
        # label_num 用于为每个岛屿分配一个唯一的标签(数字)
        label_num = 2  
        
        # max_area 用于存储最大岛屿的面积
        max_area = 0
        
        # 使用 defaultdict 存储每个标签对应的岛屿面积
        num2area = defaultdict(int)
        
        # 定义一个方向数组,用于 DFS 遍历
        directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]

        # 定义 DFS 函数以遍历和标记岛屿
        def dfs(x, y, label_num):
            # 判断坐标是否在网格内,以及是否为 '1'
            if x < 0 or x >= m or y < 0 or y >= n or grid[x][y] != 1:
                return 0
            else:
                # 标记当前单元格
                grid[x][y] = label_num
                
                # 初始化当前岛屿面积为 1
                area = 1
                
                # 遍历当前单元格的所有邻居
                for dx, dy in directions:
                    area += dfs(x + dx, y + dy, label_num)
                
                # 返回当前岛屿的面积
                return area

        # 遍历网格,查找所有岛屿并进行标记
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:
                    area = dfs(i, j, label_num)
                    max_area = max(max_area, area)  # 更新最大岛屿面积
                    num2area[label_num] = area      # 将面积存储到字典中
                    label_num += 1                  # 更新标签号

        # 遍历网格,查找可以通过改变一个 '0' 来形成的最大岛屿
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 0:
                    # 初始化 total_area 为 0
                    total_area = 0
                    
                    # 使用集合防止重复计算相同标签的岛屿面积
                    total_label_nums = set()
                    
                    # 遍历当前 '0' 的所有邻居
                    for dx, dy in directions:
                        new_x, new_y = i + dx, j + dy
                        if 0 <= new_x < m and 0 <= new_y < n and grid[new_x][new_y] != 0:
                            if grid[new_x][new_y] not in total_label_nums:
                                total_area += num2area[grid[new_x][new_y]]
                                total_label_nums.add(grid[new_x][new_y])
                    
                    # 加上新单元格的面积(即 1)
                    total_area += 1
                    
                    # 更新最大岛屿面积
                    max_area = max(max_area, total_area)

        # 返回最大岛屿面积
        return max_area