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)
总体思路
- 利用 DFS 计算出各个岛屿的面积,并标记每个 1(陆地格子)属于哪个岛。
- 遍历每个 0,统计其上下左右四个相邻格子所属岛屿的编号,去重后,累加这些岛的面积,更新答案的最大值。
为什么要去重?比如在示例 2 中,0 左边和上边的相邻格子属于同一个岛,如果直接累加面积,会重复计算。
算法
- DFS 计算出每个岛的面积,加到一个列表 area 中。
- 在 DFS 的过程中,对于访问到的格子,标记这个格子属于哪个岛。我们可以用此时此刻 area 的长度,当作岛屿的编号。
- 遍历 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这一条件用于确保在计算人工岛面积时,仅累加相邻不同岛屿的面积,避免重复计算。以下是详细解释:
- 条件分解:
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)。
思考题
463. 岛屿的周长 - 力扣(LeetCode)
解法一:
遍历每一个空格,遇到岛屿,计算其上下左右的情况,遇到水域或者出界的情况,就可以计算边了。
如图:
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)
对于不少动态规划问题,「如何想出状态定义和状态转移方程」是有套路的。我在 动态规划入门:从记忆化搜索到递推 中讲了「递归->记忆化搜索->递推」的思考套路。本文将遵照这个过程,来讲讲怎么从递归开始,一步步写出最后的递推代码。
一、寻找子问题
怎么把一个大问题变成小问题?
见微知著,想清楚最后一步发生了什么,就想清楚每一步发生了什么。
受上图启发,定义 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)。



浙公网安备 33010602011771号