Studying-代码随想录训练营day54| 110.字符串接龙、105.有向图的完全可达性、106.岛屿的周长

第53天,图论04,加强广搜和深搜的理解练习💪(ง •_•)ง,编程语言:C++

目录

110.字符串接龙

105.有向图的完全可达性

106.岛屿的周长

总结


110.字符串接龙

文档讲解:手撕字符串接龙

题目:110. 字符串接龙 (kamacoder.com)

学习:从示例中可以看出,实际上abc到def的路径不止一条,但是输出的是最短的那条路径。

因此本题主要需要解决的是两个问题:1.图中的线是如何使连在一起(图是如何构成的)2.找到最短的路径。

1.图中的线是如何连在一起:针对一个问题,首先题目中给的输入是没有包含点与点之间的连线的,图中的连线需要我们自己去连,而相连的条件也很直观,即点与点之间字符只差一个。因此我们只需要判断点与点之间的关系,如果只差一个字符,就说明是有连线的。

2.找到最短路径:这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径。因为广搜就是以起点中心向四周扩散的搜索。本题如果用深搜,会比较麻烦,要在到达终点的不同路径中选则一条最短路。 而广搜只要达到终点,一定是最短路。

注意:本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!可以使用set来检查字符串是否出现在字符串集合里更快一些。

代码:

#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <queue>
using namespace std;

int main() {
    int n; 
    cin >> n;
    string beginStr, endStr;
    cin >> beginStr >> endStr;
    
    unordered_set<string> strSet; //保存字典中的点(使用哈希表,便于查找,查找时间复杂度为O(1))
    string str;
    for (int i = 0; i < n; i++) {
        cin >> str;
        strSet.insert(str);
    }
    
    unordered_map<string, int>visitMap; //记录字符串和路径长度
    queue<string> que; //初始化队列
    que.push(beginStr);
    visitMap.insert(pair<string, int>(beginStr, 1)); //初始化visitMap
    
    while(!que.empty()) {
        string word = que.front();
        que.pop();
        int path = visitMap[word]; //这个字符串在路径中的长度

        // 开始在这个str中,挨个字符去替换
        for (int i = 0; i < word.size(); i++) {
            string newWord = word; //用一个新字符串替换str,因为每次要置换一个字符

            for (int j = 0 ; j < 26; j++) { //遍历26个字母
                newWord[i] = j + 'a';
                if (newWord == endStr) { //处理特殊情况,如果发现替换字母后,字符串与终点字符相同
                    cout <<  path + 1 << endl; // 找到了路径 
                    return 0;
                }
                //字符串集合里出现了newWord,并且newWord没有被访问过
                if (strSet.find(newWord) != strSet.end() //如果发现改完后的字符在字典中
                        && visitMap.find(newWord) == visitMap.end()) {
                    //添加访问信息,并将新字符串放到队列中
                    visitMap.insert(pair<string, int>(newWord, path + 1));
                    que.push(newWord);
                }
            }
        }
    }
    // 没找到输出0
    cout << 0 << endl;
    return 0;
}

105.有向图的完全可达性

文档讲解:手撕有向图的完全可达性

题目:105. 有向图的完全可达性 (kamacoder.com)

学习:本题定义的是一个有向图,本质上是一个有向图搜搜全路径的过程。可以采用深搜dfs 或者广搜 bfs来进行搜索。

1.采用深搜dfs:我们可以从深搜三部曲出发进行分析。

确定返回值和参数列表:首先我们需要传入用以遍历,接着我们还需要知道当前的节点key,最后我们还需要一个visited数组,来确定哪些点已经被遍历了,防止走回头路。

void dfs(const vector<list<int>>& graph, int key, vector<bool>& visited) {

确定终止条件:我们再遍历的过程中,从一个节点到另一个节点,是依据当前节点的所有出度进行的。而为了防止出现走回头路或者陷入死循环的情况(同时本题也规定了是从节点1出发到所有节点)我们用visited数组来记录访问过的节点,该节点默认数组里的元素都是false,一旦遍历到就标记为true。因此当前访问的节点如果是true,说明该节点已经访问过了,那就终止本层递归,如果不是,我们就进入递归。该终止条件可以写在进入递归后,也可以写在进入递归前。

// 写法一:处理当前访问的节点
void dfs(const vector<list<int>>& graph, int key, vector<bool>& visited) {
    if (visited[key]) {
        return;
    }
    visited[key] = true;
    list<int> keys = graph[key];
    for (int key : keys) {
        // 深度优先搜索遍历
        dfs(graph, key, visited);
    }
}

// 写法二:处理下一个要访问的节点
void dfs(const vector<list<int>>& graph, int key, vector<bool>& visited) {
    list<int> keys = graph[key];
    for (int key : keys) {
        if (visited[key] == false) { // 确认下一个是没访问过的节点
            visited[key] = true;
            dfs(graph, key, visited);
        }
    }

 处理目前搜索节点出发的路径:本题的处理逻辑实际上很简单,就是遍历到节点就把其标记为true,没有则进入新的循环,直到把所有的true都标记完。本题也没有回溯操作,因为我们不需要保存路径只需要标记即可。

代码:最后我们可以得到代码(dfs)

#include <iostream>
#include <vector>
#include <list>
using namespace std;

//1.确定返回值和参数列表
void dfs(const vector<list<int>>& graph, int key, vector<bool>& visited) { //什么时候能加const取决于,graph是否需要改变
    list<int> keys = graph[key]; //邻接表方式
    for (int key : keys) {
        //2.确定终止条件:将终止条件写在进入递归前,如果不符合直接不进入递归
        if (visited[key] == false) { //找到下一个没有访问过的点
            visited[key] = true;
            dfs(graph, key, visited);
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    
    int s,t;
    vector<list<int>> graph(n + 1); //节点编号从1到n,所以申请 n+1 这么大的数组(采用邻接表的方式)
    while (m--) {
        cin >> s >> t;
        graph[s].push_back(t);
    }
    vector<bool> visited(n + 1, false);
    visited[1] = true; // 节点1 预先处理
    dfs(graph, 1, visited);
    //检查是否都访问到了
    for (int i = 1; i <= n; i++) {
        if (visited[i] == false) { //如果存在没有访问过的点,则说明有向图不具备完全可达性
            cout << -1 << endl;
            return 0;
        }
    }
    cout << 1 << endl;
}

代码:采用bfs的方法,我们也可以通过一个count进行计数,假如count = n则说明每个点都遍历到了。

#include <iostream>
#include <vector>
#include <list>
#include <queue>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    int s, t;
    vector<list<int>> graph(n + 1); //保存地图,同时节点编号是从1开始的,因此开拓n+1的空间
    while (m--) {
        cin >> s >> t;
        graph[s].push_back(t);
    }
    vector<bool> visited(n + 1, false); //访问数组
    visited[1] = true; //初始化visited数组
    queue<int> que; //设置队列准备开始遍历
    que.push(1); //初始化队列

    // BFS
    int count = 1;
    while (!que.empty()) {
        int key = que.front(); 
        que.pop();
        list<int> keys = graph[key]; //邻接表的方式
        for (int key : keys) {
            if (!visited[key]) { //如果这个点没有被访问
                que.push(key);
                visited[key] = true;
                count++;
            }
        }
    }
    if(count == n) {
        cout << 1 << endl;
    }
    else {
        cout << -1 << endl;
    }
    return 0;
}

106.岛屿的周长

文档讲解:手撕岛屿的周长

题目:106. 岛屿的周长 (kamacoder.com)

学习:本题是求岛屿的周长,实际上本题不需要进行dfs或者bfs搜索方式,因为本题求周长更多的是一个数学问题。本题的解法有两种

第一种:根据题意,我们可以发现如果一块陆地:边是靠水的,或者是边界,则说明该边是外边,即属于周长计算里面;而边如果是靠陆地的,则说明该边是内边,不包含周长里面的。

因此我们可以通过遍历地图,找到一个陆地之后,就判断其上下左右的状态,如果是外边,则周长+1,如果是内边则周长不变,最后我们就可以求得岛屿的周长。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<vector<int>> grid(n, vector<int>(m, 0)); //保存地图
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            cin >> grid[i][j]; 
        }
    }
    int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; //四个方向
    int result = 0;
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            if(grid[i][j] == 1) { //找到了一个土地
                for(int k = 0; k < 4; k++) {
                    int nextx = i + dir[k][0];
                    int nexty = j + dir[k][1];
                    //两种情况,在四条边界处或者是海洋
                    if(nextx < 0 || nexty < 0 || nextx >=n || nexty >= m || grid[nextx][nexty] == 0) {
                        result++;
                    }
                }
            }
        }
    }
    cout << result << endl;
    return 0;
}

第二种:我们可以发现,岛屿的中边长,为岛屿的陆地数量*4,而每有一个陆地和另一个陆地是相邻的,边的数量-2,因此最后岛屿的周长就为:岛屿的陆地数量*4 - cover*2(cover表示相邻的边的数量)

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<vector<int>> grid(n, vector<int>(m, 0)); //保存地图
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            cin >> grid[i][j]; 
        }
    }
    int sum = 0;
    int cover = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 1) {
                sum++; // 统计总的陆地数量
                //为了避免重复计算,我们只统计每一块陆地上边和左边的相邻情况,这样就能够覆盖所有的情况
                // 统计上边相邻陆地
                if(i - 1 >= 0 && grid[i - 1][j] == 1) cover++;
                // 统计左边相邻陆地
                if(j - 1 >= 0 && grid[i][j - 1] == 1) cover++;
            }
        }
    }
    cout << sum*4 - cover*2 << endl;
    return 0;
}
    

总结

今天的三道题是三种完全不同的解法和思路。

第一道题,告诉了我们图的边没有给我们的时候,是如何进行边的构造的。同时如何采用bfs方式找到最短的路径。

第二道题,是有向图搜索全路径的问题,重点在于dfs和bfs的基础考察,属于图的遍历问题。

第三道题,是让我们不要盲目在图论中使用dfs和bfs,还是要读懂题意,理解题意,才能找到解题的关键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值