当前位置: 首页 > news >正文

【小白笔记】岛屿的周长(Island Perimeter)

这是一个经典的 岛屿的周长(Island Perimeter)问题,属于“网格遍历”和“计数”类型。

核心概念解释

  1. 周长 (Perimeter): 围绕一个形状的边界的总长度。在本题中,它是组成岛屿的所有陆地单元格与水域(或网格边界)相邻的边的数量总和。
  2. 陆地 (Land)grid[i][j]=1grid[i][j] = 1grid[i][j]=1 的单元格。
  3. 水域 (Water)grid[i][j]=0grid[i][j] = 0grid[i][j]=0 的单元格。
  4. 相连 (Connected): 只能通过水平垂直方向(上下左右)相邻。

算法思路

对于一个由多个边长为 1 的正方形组成的岛屿,计算其周长,我们不需要进行复杂的图形识别或搜索。我们可以从 局部 的角度来考虑:

对于每一个单独的陆地单元格(‘1’):

  1. 一个孤立的陆地单元格的周长是 4
  2. 每当一个陆地单元格与另一个相邻的陆地单元格相连时,它们之间就会有 两条 共用的边(一条属于前者,一条属于后者)。
  3. 因此,计算总周长的方法可以简化为:

总周长 = (所有陆地单元格的初始周长总和) - (所有相邻陆地单元格共用的边数)

或者,更直观的方法是:

总周长 = 遍历所有陆地单元格,统计每个单元格有多少条边是与水域(或网格边界)相邻的。

具体步骤:

  1. 初始化周长: 设定 perimeter=0perimeter = 0perimeter=0
  2. 遍历网格: 逐行逐列地检查网格中的每一个单元格 grid[r][c]grid[r][c]grid[r][c]
  3. 找到陆地: 如果当前单元格是 1(陆地),则开始计算它的贡献。
  4. 检查四个方向: 检查当前陆地单元格 grid[r][c]grid[r][c]grid[r][c] 上、下、左、右四个相邻的单元格 grid[nr][nc]grid[nr][nc]grid[nr][nc]
    • 初始周长贡献为 4
    • 每当一个相邻方向是陆地 (‘1’),就意味着当前单元格 grid[r][c]grid[r][c]grid[r][c] 在该方向上的一条边被内部消化了,它不属于周长。因此,该陆地单元格对周长的贡献要 减 1
    • 每当一个相邻方向是水域 (‘0’) 或超出了网格边界,就意味着当前单元格 grid[r][c]grid[r][c]grid[r][c] 在该方向上的一条边是岛屿的边界,它属于周长。因此,该陆地单元格对周长的贡献要 加 1

由于一个陆地单元格的初始周长是 4,我们只需检查其四个邻居:每有一个邻居是陆地,周长就减 1。

等效简化算法:

对于每一个 grid[r][c]=1grid[r][c] = 1grid[r][c]=1 的单元格:

  1. 首先假定它的贡献是 4
  2. 检查它的四个邻居:
    • 如果邻居 grid[nr][nc]grid[nr][nc]grid[nr][nc] 是陆地(1)且在网格内,则 perimeterperimeterperimeter 减 1
  3. 累加到总 perimeterperimeterperimeter 中。

示例代码(Python)

class Solution:def islandPerimeter(self, grid: list[list[int]]) -> int:if not grid or not grid[0]:return 0rows = len(grid)cols = len(grid[0])perimeter = 0# 定义四个方向的位移# (dr, dc) 分别代表 (行变化, 列变化): 上, 下, 左, 右directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]# 遍历整个网格for r in range(rows):for c in range(cols):# 只关心陆地单元格if grid[r][c] == 1:# 初始周长贡献为 4current_perimeter = 4# 检查四个邻居for dr, dc in directions:nr, nc = r + dr, c + dc# 判断邻居是否在网格内if 0 <= nr < rows and 0 <= nc < cols:# 如果邻居也是陆地,则共用了一条边,周长减 1if grid[nr][nc] == 1:current_perimeter -= 1# 否则 (邻居在网格外或邻居是水域 '0'),# 该边都是周长的一部分,保持 current_perimeter 不变# 因为:# 1. 邻居在网格外:该边是边界,属于周长# 2. 邻居是 '0' (水):该边是水边,属于周长# 累加当前陆地单元格的周长贡献perimeter += current_perimeterreturn perimeter# 示例 1 测试
grid1 = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
sol = Solution()
print(f"示例 1 的岛屿周长: {sol.islandPerimeter(grid1)}") # 输出: 16# 示例 2 测试
grid2 = [[1]]
print(f"示例 2 的岛屿周长: {sol.islandPerimeter(grid2)}") # 输出: 4

复杂度分析

  • 时间复杂度O(M×N)O(M \times N)O(M×N)。其中 MMM 是行数,NNN 是列数。我们只需要遍历网格中的每个单元格一次,并在每个单元格上执行固定的 4 次邻居检查。
  • 空间复杂度O(1)O(1)O(1)。我们只使用了几个固定的变量来存储周长和方向,没有使用额外的与输入规模相关的存储空间。

for dr, dc in directions:nr, nc = r + dr, c + dc

一个坐标层面的循环(也可以叫方向循环)

drdcnrnc 都是常用的缩写,它们代表的英文含义如下:

缩写含义解释

缩写英文全称 (English Full Name)中文含义作用和背景
drdelta Row行的增量/变化量表示从当前行 rrr 移动到相邻行的行索引变化值。
dcdelta Column列的增量/变化量表示从当前列 ccc 移动到相邻列的列索引变化值。
nrNext Row下一个行索引通过 r+drr + drr+dr 计算出的相邻单元格的行索引。
ncNext Column下一个列索引通过 c+dcc + dcc+dc 计算出的相邻单元格的列索引。

涉及的英文单词解释

  1. DeltaΔ\DeltaΔ):

    • 词源来历: 源自希腊字母表的第四个字母 Δ\DeltaΔ(大写)。在数学和科学中,Δ\DeltaΔ 常用作符号,表示变化量(Change)或差值(Difference)。
    • 在代码中: dr\text{dr}drdc\text{dc}dc 中的 d\text{d}d 就是 Delta\text{Delta}Delta 的缩写,代表行和列索引的变动。
  2. Row

    • 含义: 。在矩阵或表格中,水平排列的一组数据。
    • 对应缩写: dr\text{dr}dr 中的 R\text{R}Rnr\text{nr}nr 中的 R\text{R}R
  3. Column

    • 含义: 。在矩阵或表格中,垂直排列的一组数据。
    • 对应缩写: dc\text{dc}dc 中的 C\text{C}Cnc\text{nc}nc 中的 C\text{C}C
  4. Next

    • 含义: 下一个。表示紧接着当前位置的相邻位置。
    • 对应缩写: nr\text{nr}nrnc\text{nc}nc 中的 N\text{N}N

代码的作用

在算法中,directions 通常定义为:

directions = [(0, 1),   # 右 (dc = +1)(0, -1),  # 左 (dc = -1)(1, 0),   # 下 (dr = +1)(-1, 0)   # 上 (dr = -1)
]

循环的作用就是:通过遍历每一个 dr\text{dr}drdc\text{dc}dc 组合,计算出当前点 (r,c)(r, c)(r,c)下一个相邻点 (nr,nc)(nr, nc)(nr,nc)


🧭 一、背景:我们在一个网格中

假设有一个 3×3 的网格:

c=0c=1c=2
r=0(0,0)(0,1)(0,2)
r=1(1,0)(1,1)(1,2)
r=2(2,0)(2,1)(2,2)

每个格子都有一个“坐标” (r, c)


🧮 二、定义四个方向

在代码中:

directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

这四个方向是相对坐标变化量,可以理解成“偏移量 (delta)”:

方向drdc含义
(0, 1)0+1向右移动一格
(0, -1)0-1向左移动一格
(1, 0)+10向下移动一格
(-1, 0)-10向上移动一格

🔁 三、开始循环

当我们在一个格子,比如 (r, c) = (1, 1) 时,
执行:

for dr, dc in directions:nr, nc = r + dr, c + dc

这个循环会依次产生 4 组“邻居坐标”👇

dr, dc计算 nr, nc = r+dr, c+dc结果方向
(0, 1)(1+0, 1+1)(1, 2)右边
(0, -1)(1+0, 1-1)(1, 0)左边
(1, 0)(1+1, 1+0)(2, 1)下边
(-1, 0)(1-1, 1+0)(0, 1)上边

🧠 四、逻辑理解

这段循环做的事情是:

「从当前格子出发,把上下左右四个邻居都找出来,逐个检查它们的状态(水还是陆地?是否越界?)」


🔍 五、举个小例子

假设当前 (r, c) = (0, 0),位于左上角:

dr, dcnr, nc是否越界说明
(0, 1)(0, 1)✅ 在网格内右边
(0, -1)(0, -1)❌ 越界左边没有格子
(1, 0)(1, 0)✅ 在网格内下边
(-1, 0)(-1, 0)❌ 越界上边没有格子

程序会判断:

if 0 <= nr < rows and 0 <= nc < cols:# 邻居在网格内

🧩 六、总结一句话

for dr, dc in directions 是在循环四个方向的偏移量
nr, nc = r + dr, c + dc 是计算邻居的坐标

它的作用就是:从当前格子出发,访问上下左右四个相邻格子。


💡 记忆方法:
你可以想象成:

“我站在 (r, c) 这个格子里,看四周四个方向(上、下、左、右),
每看一个方向,就把那个格子的坐标算出来 —— 这就是 (r+dr, c+dc)。”


代码的“直觉理解”:每个陆地格子都“自带 4 条边”

我们先假设有一个陆地格子:

1
  • 如果它孤立存在(周围全是水或边界),它有 4 条边
  • 如果它的右边也有陆地,那这条右边就“合并”了,不再算边;
  • 同理,上下左右凡是有邻居为 1,就各减去 1 条边。

🔹 举例:

假设这是一个岛:

1 1
1 1

每个小格都自带 4 条边,一共 4×4=16
但中间共用的边要减掉:

共享边方向数量每条边重复算了两次实际要减去几条
水平相邻2每对格子共用 1 条边2
垂直相邻2每对格子共用 1 条边2

所以:
总周长 = 16 - 2 - 2 = 12


🧩 二、代码逻辑和思路

让我们配合伪代码拆一下逻辑:

if grid[r][c] == 1:        # 如果是陆地current_perimeter = 4  # 每个陆地初始有4条边for dr, dc in directions:   # 检查四个方向(上、下、左、右)nr, nc = r + dr, c + dcif 0 <= nr < rows and 0 <= nc < cols:  # 邻居在网格内if grid[nr][nc] == 1:              # 邻居也是陆地current_perimeter -= 1         # 共用一条边 → 减1perimeter += current_perimeter             # 加到总周长

🔁 这意味着:

  • 你遍历所有陆地;
  • 每个陆地默认有 4 条边;
  • 检查四周:每遇到一个“陆地邻居”,就少 1 条边;
  • 全部格子算完后,所有边都正确统计到。

计算个体贡献,然后累加到总体。

1. 局部计算:current_perimeter -= 1

这行代码发生在 for dr, dc in directions: 循环内部,用于确定一个陆地单元格 grid[r][c]grid[r][c]grid[r][c] 有多少条边是内接(被相邻的陆地单元格抵消)的。

  • 逻辑: 当 grid[r][c]grid[r][c]grid[r][c] 的一个邻居 grid[nr][nc]grid[nr][nc]grid[nr][nc] 也是陆地 (‘1’) 时,它们之间有一条共用的边。这条边不能算作岛屿的周长。
  • 操作: 我们最初假定 grid[r][c]grid[r][c]grid[r][c] 的周长贡献是 444。每发现一条共用的边,就将这个局部周长 current_perimeter 减去 111

总结: current_perimeter 是在检查完所有四个邻居后,当前单元格 (r,c)(r, c)(r,c) 对周长的实际贡献值

2. 全局累加:perimeter += current_perimeter

这行代码发生在 for r in range(rows):for c in range(cols): 的循环内部,但位于检查当前单元格 (r,c)(r, c)(r,c) 的所有邻居的外部(即在 for dr, dc in directions: 循环结束后)。

  • 逻辑: 完成对单元格 grid[r][c]grid[r][c]grid[r][c] 的四个邻居的检查和 current_perimeter 的计算后,我们就得到了这个单元格对总周长的最终贡献。
  • 操作: 将这个个体贡献值 current_perimeter 累加到存储总周长的变量 perimeter 上。

总结: perimeter 是所有陆地单元格的实际周长贡献的总和,是最终的答案。

关系图解

  1. 初始化grid[r][c]=1grid[r][c] = 1grid[r][c]=1 时,current_perimeter = 4
  2. 局部处理(444 次循环)
    • IF 邻居是陆地:current_perimeter = current_perimeter - 1
    • ELSE 邻居是水/边界:current_perimeter 不变
  3. 结果: 循环结束后,current_perimeter 可能是 4,3,2,14, 3, 2, 14,3,2,1000
  4. 全局累加perimeter=perimeter+current_perimeterperimeter = perimeter + current\_perimeterperimeter=perimeter+current_perimeter

这两行代码体现了“分而治之”的思想:先独立计算每个陆地格子的净周长贡献,再将所有贡献相加得到整个岛屿的总周长。

在这个内部的 for 循环中,current_perimeter 最多只能减少 444 次。**

详细解释

  1. 初始值(最多 444

    • current_perimeter = 4: 算法基于一个基本事实——每个边长为 1 的正方形(陆地单元格)最多只有 444 条边。
  2. 循环次数(固定 444 次)

    • for dr, dc in directions:directions 列表(通常是 [(0, 1), (0, -1), (1, 0), (-1, 0)])只有 444 个元素,代表上、下、左、右 444 个固定的方向。因此,这个 for 循环只执行 444
  3. 减少条件(最多 444 次满足)

    • if grid[nr][nc] == 1:: 每次循环中,只有当满足两个条件时才会执行 current_perimeter -= 1
      • 邻居在网格内部(0 <= nr < rows and 0 <= nc < cols)。
      • 邻居是陆地(grid[nr][nc] == 1)。
    • 因为循环只执行 444 次,所以这个减少操作 current_perimeter -= 1最多只能被执行 444

极端情况举例

陆地单元格位置邻居 (‘1’) 个数current_perimeter 变化最终 current_perimeter意义
孤立点 (周围都是 ‘0’ 或边界)04 - 0 = 44贡献了 444 条边到周长
角点 (被 222 个陆地邻居包围)24 - 2 = 22贡献了 222 条边到周长
边点 (被 333 个陆地邻居包围)34 - 3 = 11贡献了 111 条边到周长
中心点 (被 444 个陆地邻居包围)44 - 4 = 00贡献了 000 条边到周长

因此,这个算法设计的核心正是基于每个方格最多 444 条边的事实,通过 444 次固定的检查来准确计算其净贡献。


🧩 回顾:整体框架

你可以先记住,这段代码有三层逻辑:

1️⃣ 外层两重 for 循环:遍历每个格子
2️⃣ 内层 for 循环:检查当前格子的四个方向(上、下、左、右)
3️⃣ 判断条件
 → 如果邻居是陆地,就把当前格子的边数减 1


🧱 举个例子(直观)

我们用一个 3×3 的小网格来“跑一遍”:

grid = [[0, 1, 0],[1, 1, 1],[0, 1, 0]
]

岛屿长这样:

 010111010

第一步:定义方向

directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

这 4 个方向可以让我们从当前格子 (r, c) 移动到四个相邻格子 (nr, nc)。


第二步:外层循环(从左上角开始扫描)

程序执行顺序:

for r in range(rows):     # 一行一行for c in range(cols): # 每行从左到右

它依次访问这些格子:
(0,0) → (0,1) → (0,2) → (1,0) → (1,1) → (1,2) → (2,0) → (2,1) → (2,2)


🔍 第三步:当遇到陆地时(grid[r][c] == 1)

我们重点看几个格子的过程👇:


🟩 例子 1:格子 (0,1)

它是陆地(grid[0][1] == 1),所以开始执行:

current_perimeter = 4

默认它有 4 条边。

然后依次检查四个方向:

方向计算 nr, ncgrid[nr][nc] 值动作
右 (0,1)(0,2)0水,不减
左 (0,-1)越界不存在边界,不减
下 (1,1)1陆地 → -1
上 (-1,1)越界不存在边界,不减

🔹 结果:
current_perimeter = 4 - 1 = 3
→ 当前格子的周长贡献 = 3


🟩 例子 2:格子 (1,1)

这是中间的格子,四面都有邻居:

方向计算 nr, ncgrid[nr][nc] 值动作
(1,2)1-1
(1,0)1-1
(2,1)1-1
(0,1)1-1

🔹 结果:
current_perimeter = 4 - 4 = 0
→ 它完全被包围,不贡献周长


🟩 例子 3:格子 (2,1)

方向计算 nr, ncgrid[nr][nc] 值动作
(2,2)0水,不减
(2,0)0水,不减
(3,1)越界边界,不减
(1,1)1-1

🔹 结果:
current_perimeter = 4 - 1 = 3


📊 第四步:累加结果

每遇到一个陆地格子:

perimeter += current_perimeter

程序会把每个陆地的“贡献周长”都加起来。

最后返回:

return perimeter

总结运行逻辑图(文字版)

for 每一行for 每一列if 当前格是陆地:current_perimeter = 4for 四个方向:if 邻居在范围内 且 是陆地:current_perimeter -= 1总周长 += current_perimeter
return 总周长
http://www.dtcms.com/a/499181.html

相关文章:

  • 【C# OOP 入门到精通】从基础概念到 MVC 实战(含 SOLID 原则与完整代码)
  • 安徽省建设厅官网南宁seo外包要求
  • 算法实现迭代4_冒泡排序
  • uploads-labs靶场通关(1)
  • 网站建设标准合同福州做网站的公司多少钱
  • 类转函数(Class to Function)
  • Java-153 深入浅出 MongoDB 全面的适用场景分析与选型指南 场景应用指南
  • Makefile 模式规则精讲:从 ​​%.o: %.c​​ 到静态模式规则的终极自动化
  • app免费下载网站地址进入产品做网站如何谁来维护价格
  • 网站开发客户流程 6个阶段自助贸易网
  • Java前缀和算法题目练习
  • 《Python 结构化模式匹配深度解析:从语法革新到实战应用》
  • h5游戏免费下载:机甲战士
  • 接口测试 | 使用Postman实际场景化测试
  • 键盘事件对网站交互商业网站设计的基本原则
  • 设计模式的底层原理——解耦
  • 蚌埠市重点工程建设管理局网站国家住房与城乡建设部网站
  • USB 特殊包 --PRE
  • 十六、kubernetes 1.29 之 集群安全机制
  • 固定资产使用年份入错了怎么调整?
  • Linux Shell 正则表达式:从入门到实战,玩转文本匹配与处理
  • 网站建设的功能有哪些内容在线医生免费咨询
  • Gituee
  • 简洁软件下载网站源码做网站服务器多钱
  • java.nio 包详解
  • python+django/flask婚纱摄影拍照管理系统
  • SpringBoot 集成 ELK 实现系统操作日志存储方案
  • 如何解决 Jacob 与 Tomcat 类加载问题:深入分析 Tomcat 类加载机制与 JVM 双亲委派机制
  • AVL树(平衡二叉树)详细介绍与Java实现
  • 2025年市场岗位能力重构与跨领域转型路径分析