代码随想录算法训练营 Day46 | 图论 part04

106. 海岸线计算

题目描述

给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。

你可以假设矩阵外均被水包围。在矩阵中恰好拥有一个岛屿,假设组成岛屿的陆地边长都为 1,请计算海岸线,即:岛屿的周长。岛屿内部没有水域。

输入描述

第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。

输出描述

输出一个整数,表示岛屿的周长。

输入示例

复制代码
5 5
0 0 0 0 0 
0 1 0 1 0
0 1 1 1 0
0 1 1 1 0
0 0 0 0 0

输出示例

复制代码
14
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

// 四个方向:右、下、左、上
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> graph(n, vector<int>(m));
    // 输入网格
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> graph[i][j];
        }
    }
    int res = 0; // 记录周长
    // 遍历每一个格子
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            // 如果是陆地
            if (graph[i][j] == 1) {
                // 检查四个方向
                for (int k = 0; k < 4; k++) {
                    int curx = i + dir[k][0];
                    int cury = j + dir[k][1];
                    // 如果越界 或 邻居是海洋,则贡献一条边
                    if (curx < 0 || curx >= n ||
                        cury < 0 || cury >= m ||
                        graph[curx][cury] == 0) {
                        res++;
                    }
                }
            }
        }
    }
    // 输出总周长
    cout << res << endl;
    return 0;
}

总结

1. 核心思路

遍历所有陆地格子,每个格子向四个方向检查:

  • 如果该方向是海洋或越界,就说明这条边属于周长

最终把所有这样的边累加起来,就是总周长。

2. 本质理解

每个陆地最多贡献 4 条边:

  • 邻居是陆地 → 不算
  • 邻居是海洋 / 越界 → 算 1 条边
3. 实现要点
  • 四方向遍历
  • 判断越界 + 海洋
  • 不需要 DFS / BFS
4. 复杂度
  • 时间复杂度:O(n * m)
  • 空间复杂度:O(1)(不考虑输入)

110. 字符串迁移

题目描述

字典 strList 中从字符串 beginStr 和 endStr 的转换序列是一个按下述规格形成的序列:

  1. 序列中第一个字符串是 beginStr。

  2. 序列中最后一个字符串是 endStr。

  3. 每次转换只能改变一个字符。

  4. 转换过程中的中间字符串必须是字典 strList 中的字符串,且strList里的每个字符串只用使用一次。

给你两个字符串 beginStr 和 endStr 和一个字典 strList,找到从 beginStr 到 endStr 的最短转换序列中的字符串数目。如果不存在这样的转换序列,返回 0。

输入描述

第一行包含一个整数 N,表示字典 strList 中的字符串数量。 第二行包含两个字符串,用空格隔开,分别代表 beginStr 和 endStr。 后续 N 行,每行一个字符串,代表 strList 中的字符串。

输出描述

输出一个整数,代表从 beginStr 转换到 endStr 需要的最短转换序列中的字符串数量。如果不存在这样的转换序列,则输出 0。

输入示例

复制代码
6
abc def
efc
dbc
ebc
dec
dfc
yhn

输出示例

复制代码
4
cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <unordered_set>
using namespace std;

int main() {
    int n;
    cin >> n;
    string beginStr, endStr, str;
    cin >> beginStr >> endStr;
    // 存储字典中的所有单词,方便 O(1) 查询
    unordered_set<string> ust;
    for (int i = 0; i < n; i++) {
        cin >> str;
        ust.insert(str);
    }
    queue<string> que;
    // key:单词
    // value:从 beginStr 到该单词的路径长度
    unordered_map<string, int> ump;
    // 起始单词入队
    que.push(beginStr);
    ump[beginStr] = 1;
    // BFS 搜索最短转换路径
    while (!que.empty()) {
        string word = que.front();
        que.pop();
        int path = ump[word];
        // 枚举当前单词的每一位
        for (int i = 0; i < word.size(); i++) {
            string newWord = word;
            // 将当前位替换成 a ~ z
            for (int j = 0; j < 26; j++) {
                newWord[i] = 'a' + j;
                // 如果变换后等于目标单词,直接输出最短路径
                if (newWord == endStr) {
                    cout << path + 1 << endl;
                    return 0;
                }
                // 如果新单词在字典中,并且之前没有访问过
                else if (ust.find(newWord) != ust.end() &&
                         !ump.count(newWord)) {
                    ump[newWord] = path + 1;
                    que.push(newWord);
                }
            }
        }
    }
    // 无法转换到目标单词
    cout << 0 << endl;
    return 0;
}

总结

1. 核心思路

这题是典型的 最短路径问题,使用 BFS 解决。

每个单词可以看作一个节点,

如果两个单词只差一个字符,就可以看作它们之间有一条边。

beginStr 开始 BFS,第一次遇到 endStr 时,路径长度就是最短转换长度。

2. 实现方式

对当前单词的每一位进行枚举,尝试将它替换成 a ~ z

如果替换后的单词在字典中,并且没有访问过,就加入队列继续搜索。

3. 关键点
  • unordered_set:快速判断单词是否在字典中
  • unordered_map:记录每个单词对应的路径长度,同时也起到 visited 的作用
  • BFS:保证第一次到达目标单词时路径最短
4. 复杂度分析

设单词数量为 n,单词长度为 L

  • 时间复杂度:O(n * L * 26)
  • 空间复杂度:O(n)

105. 有向图的完全联通

题目描述

给定一个有向图,包含 N 个节点,节点编号分别为 1,2,...,N。现从 1 号节点开始,如果可以从 1 号节点的边可以到达任何节点,则输出 1,否则输出 -1。

输入描述

第一行包含两个正整数,表示节点数量 N 和边的数量 K。 后续 K 行,每行两个正整数 s 和 t,表示从 s 节点有一条边单向连接到 t 节点。

输出描述

如果可以从 1 号节点的边可以到达任何节点,则输出 1,否则输出 -1。

输入示例

复制代码
4 4
1 2
2 1
1 3
2 4

输出示例

复制代码
1
cpp 复制代码
// DFS
#include <iostream>
#include <vector>
using namespace std;

// DFS:从起点出发,标记所有能到达的节点
void dfs(vector<vector<int>>& graph, vector<bool>& visited, int x) {
    // 如果已经访问过,直接返回
    if (visited[x]) return;
    // 标记当前节点
    visited[x] = true;
    // 遍历所有邻接节点
    for (int i : graph[x]) {
        dfs(graph, visited, i);
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    // 邻接表
    vector<vector<int>> graph(n + 1);
    vector<bool> visited(n + 1, false);
    // 输入边(有向图)
    for (int i = 0; i < m; i++) {
        int s, t;
        cin >> s >> t;
        graph[s].push_back(t);
    }
    int start = 1;
    // 从 1 开始 DFS
    dfs(graph, visited, start);
    // 判断是否所有点都能到达
    for (int i = 2; i <= n; i++) {
        if (!visited[i]) {
            cout << -1 << endl;
            return 0;
        }
    }
    cout << 1 << endl;
    return 0;
}



// BFS
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

// BFS:从起点出发,层序遍历图
void bfs(vector<vector<int>>& graph, vector<bool>& visited, int x) {
    queue<int> que;
    // 起点入队
    que.push(x);
    visited[x] = true;
    while (!que.empty()) {
        int cur = que.front();
        que.pop();
        // 遍历邻接节点
        for (int i : graph[cur]) {
            if (!visited[i]) {
                que.push(i);
                visited[i] = true;
            }
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    // 邻接表
    vector<vector<int>> graph(n + 1);
    vector<bool> visited(n + 1, false);
    // 输入边(有向图)
    for (int i = 0; i < m; i++) {
        int s, t;
        cin >> s >> t;
        graph[s].push_back(t);
    }
    int start = 1;
    // 从 1 开始 BFS
    bfs(graph, visited, start);
    // 判断是否所有点都能到达
    for (int i = 2; i <= n; i++) {
        if (!visited[i]) {
            cout << -1 << endl;
            return 0;
        }
    }
    cout << 1 << endl;
    return 0;
}

总结

1. 核心思路

从节点 1 出发,判断是否能到达图中所有节点。

本质是:
判断图的连通性(从 1 出发是否能遍历所有点)

2. 实现要点
  • 使用邻接表存图
  • 1 开始 DFS / BFS
  • visited 标记访问过的节点
  • 最后检查是否所有节点都被访问
3. DFS vs BFS
  • DFS:递归实现,代码简洁
  • BFS:队列实现,更稳定

两者本质一样,都是图遍历

4. 复杂度
  • 时间复杂度:O(n + m)
  • 空间复杂度:O(n + m)
相关推荐
拾-光2 小时前
LTX-Video 2.3 实战:用图片生成视频,消费级显卡也能跑的开源 I2V 模型(GPT Image 2)
java·人工智能·python·深度学习·算法·机器学习·音视频
小O的算法实验室2 小时前
2026年ESWA,考虑曲率约束路径优化的 Dubins-RRT* 运动规划算法,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
jllllyuz2 小时前
灰狼算法优化的LSSVR程序
算法
杨校2 小时前
杨校老师课堂之栈结构的专项训练
算法
故事和你912 小时前
洛谷-算法2-2-常见优化技巧3
开发语言·数据结构·c++·算法·深度优先·动态规划·图论
菜鸟555552 小时前
2025江西省CCPC省赛暨全国邀请赛(南昌)
数据结构·c++·算法·acm·思维·ccpc·xcpc
lds走自己的路3 小时前
全局坐标转局部坐标推导
人工智能·算法·机器学习
杨校3 小时前
杨校老师课堂之C++高精度乘法
算法
上弦月-编程3 小时前
C语言位运算:从入门到精通
运维·c语言·开发语言·vscode·算法·leetcode·极限编程