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

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

核心概念解释

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

算法思路

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

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

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

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

或者,更直观的方法是:

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

具体步骤:

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

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

等效简化算法:

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

  1. 首先假定它的贡献是 4
  2. 检查它的四个邻居:
    • 如果邻居 g r i d [ n r ] [ n c ] grid[nr][nc] grid[nr][nc] 是陆地(1)且在网格内 ,则 p e r i m e t e r perimeter perimeter 减 1
  3. 累加到总 p e r i m e t e r perimeter perimeter 中。

示例代码(Python)

python 复制代码
class Solution:
    def islandPerimeter(self, grid: list[list[int]]) -> int:
        if not grid or not grid[0]:
            return 0
        
        rows = 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:
                    # 初始周长贡献为 4
                    current_perimeter = 4
                    
                    # 检查四个邻居
                    for dr, dc in directions:
                        nr, nc = r + dr, c + dc
                        
                        # 判断邻居是否在网格内
                        if 0 <= nr < rows and 0 <= nc < cols:
                            # 如果邻居也是陆地,则共用了一条边,周长减 1
                            if grid[nr][nc] == 1:
                                current_perimeter -= 1
                        
                        # 否则 (邻居在网格外或邻居是水域 '0'),
                        # 该边都是周长的一部分,保持 current_perimeter 不变
                        # 因为:
                        # 1. 邻居在网格外:该边是边界,属于周长
                        # 2. 邻居是 '0' (水):该边是水边,属于周长

                    # 累加当前陆地单元格的周长贡献
                    perimeter += current_perimeter
                    
        return 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)。其中 M M M 是行数, N N N 是列数。我们只需要遍历网格中的每个单元格一次,并在每个单元格上执行固定的 4 次邻居检查。
  • 空间复杂度 : O ( 1 ) O(1) O(1)。我们只使用了几个固定的变量来存储周长和方向,没有使用额外的与输入规模相关的存储空间。

python 复制代码
for dr, dc in directions:
    nr, nc = r + dr, c + dc

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

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

缩写含义解释

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

涉及的英文单词解释

  1. Delta ( Δ \Delta Δ):

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

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

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

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

代码的作用

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

python 复制代码
directions = [
    (0, 1),   # 右 (dc = +1)
    (0, -1),  # 左 (dc = -1)
    (1, 0),   # 下 (dr = +1)
    (-1, 0)   # 上 (dr = -1)
]

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


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

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

c=0 c=1 c=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)


🧮 二、定义四个方向

在代码中:

python 复制代码
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

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

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

🔁 三、开始循环

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

执行:

python 复制代码
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, dc nr, nc 是否越界 说明
(0, 1) (0, 1) ✅ 在网格内 右边
(0, -1) (0, -1) ❌ 越界 左边没有格子
(1, 0) (1, 0) ✅ 在网格内 下边
(-1, 0) (-1, 0) ❌ 越界 上边没有格子

程序会判断:

python 复制代码
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


🧩 二、代码逻辑和思路

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

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

🔁 这意味着:

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

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

1. 局部计算:current_perimeter -= 1

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

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

总结: 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: 循环结束后)。

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

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

关系图解

  1. 初始化 : g r i d [ r ] [ c ] = 1 grid[r][c] = 1 grid[r][c]=1 时,current_perimeter = 4
  2. 局部处理( 4 4 4 次循环)
    • IF 邻居是陆地:current_perimeter = current_perimeter - 1
    • ELSE 邻居是水/边界:current_perimeter 不变
  3. 结果 : 循环结束后,current_perimeter 可能是 4 , 3 , 2 , 1 4, 3, 2, 1 4,3,2,1 或 0 0 0。
  4. 全局累加 : p e r i m e t e r = p e r i m e t e r + c u r r e n t _ p e r i m e t e r perimeter = perimeter + current\_perimeter perimeter=perimeter+current_perimeter。

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

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

详细解释

  1. 初始值(最多 4 4 4)

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

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

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

极端情况举例

陆地单元格位置 邻居 ('1') 个数 current_perimeter 变化 最终 current_perimeter 意义
孤立点 (周围都是 '0' 或边界) 0 4 - 0 = 4 4 贡献了 4 4 4 条边到周长
角点 (被 2 2 2 个陆地邻居包围) 2 4 - 2 = 2 2 贡献了 2 2 2 条边到周长
边点 (被 3 3 3 个陆地邻居包围) 3 4 - 3 = 1 1 贡献了 1 1 1 条边到周长
中心点 (被 4 4 4 个陆地邻居包围) 4 4 - 4 = 0 0 贡献了 0 0 0 条边到周长

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


🧩 回顾:整体框架

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

1️⃣ 外层两重 for 循环 :遍历每个格子

2️⃣ 内层 for 循环 :检查当前格子的四个方向(上、下、左、右)

3️⃣ 判断条件

→ 如果邻居是陆地,就把当前格子的边数减 1


🧱 举个例子(直观)

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

python 复制代码
grid = [
  [0, 1, 0],
  [1, 1, 1],
  [0, 1, 0]
]

岛屿长这样:

复制代码
 010
 111
 010

第一步:定义方向

python 复制代码
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

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


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

程序执行顺序:

python 复制代码
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),所以开始执行:

python 复制代码
current_perimeter = 4

默认它有 4 条边。

然后依次检查四个方向:

方向 计算 nr, nc grid[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, nc grid[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, nc grid[nr][nc] 值 动作
(2,2) 0 水,不减
(2,0) 0 水,不减
(3,1) 越界 边界,不减
(1,1) 1 -1

🔹 结果:
current_perimeter = 4 - 1 = 3


📊 第四步:累加结果

每遇到一个陆地格子:

python 复制代码
perimeter += current_perimeter

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

最后返回:

python 复制代码
return perimeter

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

复制代码
for 每一行
    for 每一列
        if 当前格是陆地:
            current_perimeter = 4
            for 四个方向:
                if 邻居在范围内 且 是陆地:
                    current_perimeter -= 1
            总周长 += current_perimeter
return 总周长
相关推荐
霜绛3 小时前
Unity:UGUI笔记(一)——三大基础控件、组合控件
笔记·学习·unity·游戏引擎
代码or搬砖3 小时前
Git学习笔记(三)
笔记·git·学习
阿维的博客日记3 小时前
Redis学习笔记-QuickList
redis·笔记·学习
闲人编程4 小时前
深入浅出Transformer:使用Hugging Face库快速上手NLP
python·深度学习·自然语言处理·nlp·transformer·hugging face·codecapsule
今天只学一颗糖4 小时前
Linux学习笔记--GPIO子系统和PinCtrl子系统
linux·笔记·学习
爬虫程序猿4 小时前
把 1688 商品详情搬进 MySQL:PHP 爬虫全链路实战(2025 版)
爬虫·python·音视频
lingggggaaaa4 小时前
小迪安全v2023学习笔记(一百三十四讲)—— Windows权限提升篇&数据库篇&MySQL&MSSQL&Oracle&自动化项目
java·数据库·windows·笔记·学习·安全·网络安全
Q_Q5110082855 小时前
python+django/flask婚纱摄影拍照管理系统
spring boot·python·django·flask·node.js·php
可触的未来,发芽的智生5 小时前
触摸未来2025-10-18:生成文字的小宇宙矩阵溯源
人工智能·python·神经网络·程序人生·自然语言处理