5.11

200. 岛屿数量 - 力扣(LeetCode)

用循环找‘1’的点,res ++ , 同时dfs把四周走过的全标记为‘2’

class Solution {
public:
    int dirs[4][2] = {{-1 , 0} , {0 , 1} , {1 , 0} , {0 , -1}};
    int numIslands(vector<vector<char>>& grid) {
        int res = 0;
        int m = grid.size() , n = grid[0].size();
        auto dfs = [&](this auto&& dfs , int x , int y)->void{
            if(x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == '0' || grid[x][y] == '2')   return;
            grid[x][y] = '2';
            for (int i = 0; i < 4; i++) {
               int nextx = x + dirs[i][0];
               int nexty = y + dirs[i][1];              
               dfs(nextx , nexty);
            }
        };
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
               if(grid[i][j] == '1'){
                res ++;
                dfs(i , j);
               }
            }
        }
        return res;
    }
};

695. 岛屿的最大面积 - 力扣(LeetCode)

class Solution {
  public:
      int dirs[4][2] = {{-1 , 0} , {0 , 1} , {1 , 0} , {0 , -1}};
      int maxAreaOfIsland(vector<vector<int>>& grid) {
          int res = 0;
          int cnt = 0;

          int m = grid.size() , n = grid[0].size();
          auto dfs = [&](this auto&& dfs , int x , int y)->void{
              if(x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == 0)   return;
              cnt ++;
              grid[x][y] = 0;
              for (int i = 0; i < 4; i++) {
                 int nextx = x + dirs[i][0];
                 int nexty = y + dirs[i][1];              
                 dfs(nextx , nexty);
              }
          };
          for (int i = 0; i < m; i++) {
              for (int j = 0; j < n; j++) {
                 if(grid[i][j] == 1){
                  cnt = 0;//这句加不加的区别就是1.加了求单个最大面积 2.不加求总岛屿面积
                  dfs(i , j);
                  res = max(res , cnt);
                 }
              }
          }
          return res;
      }
  };

827. 最大人工岛 - 力扣(LeetCode)

总体思路

  1. 利用 DFS 计算出各个岛屿的面积,并标记每个 1(陆地格子)属于哪个岛。
  2. 遍历每个 0,统计其上下左右四个相邻格子所属岛屿的编号,去重后,累加这些岛的面积,更新答案的最大值。

为什么要去重?比如在示例 2 中,0 左边和上边的相邻格子属于同一个岛,如果直接累加面积,会重复计算

算法

  1. DFS 计算出每个岛的面积,加到一个列表 area 中。
  2. 在 DFS 的过程中,对于访问到的格子,标记这个格子属于哪个岛。我们可以用此时此刻 area 的长度,当作岛屿的编号。
  3. 遍历 grid 中的 0,用一个哈希集合记录其上下左右四个相邻格子,所属的岛屿的编号。然后遍历哈希集合,根据编号去 area 中获取到对应的面积,累加面积,更新答案的最大值。也可以在哈希集合记录的同时累加面积。

细节

为了简化代码逻辑,在记录岛屿编号时,可以把 area 的长度加 2 记录到 grid[i][j] 中。加 2 是为了和 grid 原有的值区分开。

如果不想修改 grid,也可以创建额外空间记录岛屿编号。

class Solution {
    static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public:
    int largestIsland(vector<vector<int>>& grid) {
        int n = grid.size();
        vector<int> area;
        auto dfs = [&](this auto&& dfs, int i, int j) -> int {
            grid[i][j] = area.size() + 2; // 记录 (i,j) 属于哪个岛
            int size = 1;
            for (auto& [dx, dy] : dirs) {
                int x = i + dx, y = j + dy;
                if (0 <= x && x < n && 0 <= y && y < n && grid[x][y] == 1) {
                    size += dfs(x, y);
                }
            }
            return size;
        };

        // DFS 每个岛,统计各个岛的面积,记录到 area 列表中
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    area.push_back(dfs(i, j));
                }
            }
        }

        // 特判没有岛的情况
        if (area.empty()) {
            return 1;
        }

        int ans = 0;
        unordered_set<int> s;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j]) continue;
                s.clear();
                int new_area = 1;
                for (auto& [dx, dy] : dirs) {
                    int x = i + dx, y = j + dy;
                    if (0 <= x && x < n && 0 <= y && y < n && grid[x][y] && s.insert(grid[x][y]).second) {
                        new_area += area[grid[x][y] - 2]; // 累加面积
                    }
                }
                ans = max(ans, new_area);
            }
        }

        // 考虑特殊情况:如果最后 ans 仍然为 0,说明所有格子都是 1,返回 n^2
        return ans ? ans : n * n;
    }
};

在代码中,grid[x][y] && s.insert(grid[x][y]).second 这一条件用于确保在计算人工岛面积时,仅累加相邻不同岛屿的面积,避免重复计算。以下是详细解释:

  1. 条件分解
    • grid[x][y]:检查相邻位置 (x, y) 是否为陆地(非零值)。若为 0(海洋),则条件不满足,跳过。
    • s.insert(grid[x][y]).second:将相邻岛屿的编号插入集合 s,并检查插入结果:
      • s.insert(...):返回一个 pair<iterator, bool>,其中 second 表示是否插入成功。
      • 若岛屿编号已存在,插入失败,返回 false,条件不满足,跳过。
      • 若岛屿编号不存在,插入成功,返回 true,条件满足,继续执行。

复杂度分析

  • 时间复杂度:O(n^2),其中 n 是 grid 的长度。
  • 空间复杂度:O(n^2)。

思考题

  1. 改成可以把第 i 排 第 j 列的 0 都变成 1,要怎么做?题目
  2. 改成可以把第 i 排 第 j 列的 0 都变成 1,要怎么做?题目

463. 岛屿的周长 - 力扣(LeetCode)

解法一:

遍历每一个空格,遇到岛屿,计算其上下左右的情况,遇到水域或者出界的情况,就可以计算边了。

如图:

463.岛屿的周长.png

class Solution {
  static constexpr int dirs[4][2] = {{-1 , 0} , {0 , 1} , {1 , 0} , {0 , -1}};
public:
  int islandPerimeter(vector<vector<int>>& grid) {
    int res = 0;
    int m = grid.size() , n = grid[0].size();
  
    for (int i = 0; i < m; i++) {
       for (int j = 0; j < n; j++) {
          if(grid[i][j] == 1){
            for(auto& [dx , dy] : dirs){
              int x = i + dx , y = j + dy;
              if(x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == 0)  res ++;
            }
          } 
       }
    }
    return res;
  }
};

64. 最小路径和 - 力扣(LeetCode)

对于不少动态规划问题,「如何想出状态定义和状态转移方程」是有套路的。我在 动态规划入门:从记忆化搜索到递推 中讲了「递归->记忆化搜索->递推」的思考套路。本文将遵照这个过程,来讲讲怎么从递归开始,一步步写出最后的递推代码。

一、寻找子问题

怎么把一个大问题变成小问题?

offer47.png

见微知著,想清楚最后一步发生了什么,就想清楚每一步发生了什么。

受上图启发,定义 dfs(i,j) 表示从左上角到第 i 行第 j 列这个格子(记作 (i,j))的最小价值和。

分类讨论怎么到达 (i,j):

  • 如果是从左边过来,则 dfs(i,j)=dfs(i,j−1)+grid[i][j];
  • 如果是从上边过来,则 dfs(i,j)=dfs(i−1,j)+grid[i][j]。

二者取最小值,得

dfs(i,j)=min(dfs(i,j−1),dfs(i−1,j))+grid[i][j]

递归边界

  • dfs(−1,j)=dfs(i,−1)=∞。用 ∞ 表示不合法(出界)的状态,从而保证 min 不会取到不合法的状态。
  • dfs(0,0)=grid[0][0]。

递归入口:dfs(m−1,n−1),这是原问题,也是答案。

class Solution {
  public:
      int minPathSum(vector<vector<int>>& grid) {
          int m = grid.size() , n = grid[0].size();
          vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));
          dp[0][1]  = dp[1][0] = 0;
          for (int i = 1; i <= m; i++) {
             for (int j = 1; j <= n; j++) {
                dp[i][j] = min(dp[i - 1][j] , dp[i][j - 1]) + grid[i - 1][j - 1];
             }
          }
          return dp[m][n];
      }
  };

复杂度分析

  • 时间复杂度:O(mn),其中 m 和 n 分别为 grid 的行数和列数。
  • 空间复杂度:O(mn)。

空间优化

举个例子,在计算 f[1][1] 时,会用到 f[0][1],但是之后就不再用到了。那么干脆把 f[1][1] 记到 f[0][1] 中,这样对于 f[1][2] 来说,它需要的数据就在 f[0][1] 和 f[0][2] 中。f[1][2] 算完后也可以同样记到 f[0][2] 中。

所以只需要一个长为 n+1 的一维数组就够了。本题的转移方程类似完全背包,故采用正序遍历。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid[0].size();
        vector f(n + 1, INT_MAX);
        f[1] = 0;
        for (auto& row : grid) {
            for (int j = 0; j < n; j++) {
                f[j + 1] = min(f[j], f[j + 1]) + row[j];
            }
        }
        return f[n];
    }
};

复杂度分析

  • 时间复杂度:O(mn),其中 m 和 n 分别为 grid 的行数和列数。
  • 空间复杂度:O(n)。
posted @ 2025-05-12 20:26  七龙猪  阅读(4)  评论(0)    收藏  举报
-->