代码随想录算法训练营 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)
相关推荐
澈20721 小时前
C++并查集:高效解决连通性问题
java·c++·算法
旖-旎1 天前
深搜练习(单词搜索)(12)
c++·算法·深度优先·力扣
企客宝CRM1 天前
2026年中小企业CRM选型指南:企客宝CRM处于什么位置?
android·算法·企业微信·rxjava·crm
橙淮1 天前
二叉树核心概念与Java实现详解
数据结构·算法
米罗篮1 天前
DSU并查集 & 拓展欧几里得-逆元
c++·经验分享·笔记·算法·青少年编程
橙淮1 天前
双指针法:高效算法解题的利器
算法
初心未改HD1 天前
深度学习之MLP与反向传播算法详解
人工智能·深度学习·算法
刀法如飞1 天前
【Go 字符串查找的 20 种实现方式,用不同思路解决问题】
人工智能·算法·go
技术小黑1 天前
CNN算法实战系列03 | DenseNet121算法实战与解析
pytorch·深度学习·算法·cnn