代码随想录算法训练营第54天 | 第十一章:图论part04: 110.  字符串接龙、105.  有向图的完全可达性、106.  岛屿的周长

第十一章:图论part04

110. 字符串接龙

经过上面的练习,大家可能会感觉 广搜不过如此,都刷出自信了,本题让大家初步感受一下,广搜难不在广搜本身,而是如何应用广搜。
文章讲解

思路

image.png

实际上就是求最短路径的长度

需要解决两个问题

  1. 图中的点如何连在一起的
  • 判断点与点之间是否有连线,需要判断是不是差一个字符,如果差一个字符,那就是有链接。
  1. 起点和终点的最短路径长度
  • 这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径。因为广搜就是以起点中心向四周扩散的搜索。
  • 本题如果用深搜,会比较麻烦,要在到达终点的不同路径中选则一条最短路。 而广搜只要达到终点,一定是最短路。

注意

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

我这样写过不了不知道为什么

import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();  // 输入单词列表的大小
        sc.nextLine();  // 清除缓冲区
        String[] strs = sc.nextLine().split(" ");  // 输入起始单词和结束单词

        List<String> wordList = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            wordList.add(sc.next());  // 添加每一个单词到列表中
        }

        int result = ladderLength(strs[0], strs[1], wordList);
        System.out.println(result);  // 输出结果
    }

    public static int ladderLength(String beginStr, String endStr, List<String> strList) {
        // 将单词列表转换为一个Set以便快速查找
        Set<String> wordSet = new HashSet<>(strList);

        // 如果endStr不在字典中,直接返回0,因为无法转换
        if (!wordSet.contains(endStr)) {
            return 0;
        }

        // 用于记录每个单词是否访问过,以及它在转换路径中的长度
        Map<String, Integer> pathMap = new HashMap<>();

        // 初始化队列,用于BFS遍历
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginStr);  // 将起始单词加入队列
        pathMap.put(beginStr, 1);  // 设置起始单词的路径长度为1

        // 开始BFS遍历
        while (!queue.isEmpty()) {
            // 从队列中取出一个单词
            String currentWord = queue.poll();
            int currentPath = pathMap.get(currentWord);  // 当前路径长度

            // 对单词的每一个字符进行替换尝试
            for (int i = 0; i < currentWord.length(); i++) {
                char[] wordArray = currentWord.toCharArray();  // 将字符串转换为字符数组

                // 尝试将每个字符替换为'a'到'z'中的任意一个字母
                for (char c = 'a'; c <= 'z'; c++) {
                    wordArray[i] = c;  // 替换字符
                    String newWord = new String(wordArray);  // 生成新单词

                    // 如果新单词就是目标单词,返回路径长度
                    if (newWord.equals(endStr)) {
                        return currentPath + 1;
                    }

                    // 如果新单词在字典中,且之前未被访问过
                    if (wordSet.contains(newWord) && !pathMap.containsKey(newWord)) {
                        pathMap.put(newWord, currentPath + 1);  // 记录新单词的路径长度
                        queue.offer(newWord);  // 将新单词加入队列
                    }
                }
            }
        }

        // 如果没有找到目标单词,返回0
        return 0;
    }
}

这个能过

import java.util.*;

public class Main {
    // 全局变量用于存储单词集合和路径信息
    static Set<String> wordSet = new HashSet<>();
    static Map<String, Integer> pathMap = new HashMap<>();

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();  // 输入单词列表的大小
        String beginStr = sc.next();  // 起始单词
        String endStr = sc.next();  // 目标单词
        sc.nextLine();  // 清除缓冲区

        // 将所有单词添加到wordSet中
        for (int i = 0; i < n; i++) {
            wordSet.add(sc.next());
        }

        // 初始节点的路径长度设置为1
        pathMap.put(beginStr, 1);

        // 使用队列进行广度优先搜索
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginStr);  // 将起始单词加入队列

        // 开始BFS遍历
        while (!queue.isEmpty()) {
            String currentWord = queue.poll();  // 取出当前单词
            int currentPath = pathMap.get(currentWord);  // 当前路径长度

            // 对单词的每一个字符进行替换尝试
            for (int i = 0; i < currentWord.length(); i++) {
                char[] wordArray = currentWord.toCharArray();  // 将字符串转换为字符数组

                // 尝试将每个字符替换为'a'到'z'中的任意一个字母
                for (char c = 'a'; c <= 'z'; c++) {
                    wordArray[i] = c;  // 替换字符
                    String newWord = new String(wordArray);  // 生成新单词

                    // 如果新单词就是目标单词,返回路径长度
                    if (newWord.equals(endStr)) {
                        System.out.println(currentPath + 1);
                        return;
                    }

                    // 如果新单词在字典中,且之前未被访问过
                    if (wordSet.contains(newWord) && !pathMap.containsKey(newWord)) {
                        pathMap.put(newWord, currentPath + 1);  // 记录新单词的路径长度
                        queue.offer(newWord);  // 将新单词加入队列
                    }
                }
            }
        }

        // 如果没有找到目标单词,返回0
        System.out.println(0);
    }
}

之后要放到编译器里调试一下


105. 有向图的完全可达性

深搜有细节,同样是深搜两种写法的区别,以及什么时候需要回溯操作呢?
文章讲解

思路

本题是一个有向图搜索全路径的问题。 只能用深搜(DFS)或者广搜(BFS)来搜。

深搜三部曲

1. 确认递归函数,参数

  • 需要传入地图,需要知道当前我们拿到的key,以至于去下一个房间。
  • 同时还需要一个数组,用来记录我们都走过了哪些房间,这样好知道最后有没有把所有房间都遍历的,可以定义一个一维数组。
    所以 递归函数参数如下:
// key 当前得到的key 
// visited 记录访问过的房间 
void dfs(List<List<Integer>> graph, int key, boolean[] visited) {
    // 具体逻辑在下面实现
}

2. 确认终止条件

  • 遍历的时候什么时候中止,要想清楚递归的时候是处理当前访问的节点,还是处理下一个要访问的节点。
  • 首先明确,本题中“处理” = 用 visited数组来记录访问过的节点,该节点默认 数组里元素都是false,把元素标记为true就是处理 本节点了。
  • 如果我们是处理当前访问的节点,当前访问的节点如果是 true ,说明是访问过的节点,那就终止本层递归,如果不是true,我们就把它赋值为true,因为这是我们处理本层递归的节点。
if (visited[key]) {
        return;
    }
    visited[key] = true;
    List<Integer> keys = graph.get(key);
    for (int nextKey : keys) {
        // 深度优先搜索遍历
        dfs(graph, nextKey, visited);
    }

写法二:处理下一个要访问的节点

void dfs(List<List<Integer>> graph, int key, boolean[] visited) {
    List<Integer> keys = graph.get(key);
    for (int nextKey : keys) {
        if (!visited[nextKey]) { // 确认下一个是没访问过的节点
            visited[nextKey] = true;
            dfs(graph, nextKey, visited);
        }
    }
}

3. 处理目前搜索节点出发的路径

  • 上面终止条件有两种不同的写法,所以递归也有两种写法
  • 为什么本题就没有回溯呢?只有在需要搜索一条可行路径的时候,就需要回溯操作了,因为没有回溯,就没法“调头”。本题只是求节点1能否到所有节点,就不必回溯,只要遍历过的节点都一律标记上。

写法一:DFS 处理当前访问的节点

import java.util.*;

public class Main{
    // 写法一:DFS 处理当前访问的节点
    public static void dfs(List<List<Integer>> graph, int key, boolean[] visited){
        if(visited[key]) return;
        visited[key] = true;
        List<Integer> keys = graph.get(key);
        for(int nextKey : keys){
            dfs(graph, nextKey, visited);
        }
        
    }
    
    public static void main (String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt(); 
        int K = sc.nextInt();
        List<List<Integer>> graph = new ArrayList<>(N + 1);
        for(int i = 0; i <= N; i++){
            graph.add(new ArrayList<>());
        }
        for(int i = 0; i < K; i++){
            int s = sc.nextInt();
            int t = sc.nextInt();
            graph.get(s).add(t);
        }
        boolean[] visited = new boolean[N + 1];
        dfs(graph, 1, visited);
        // 检查是否都访问到了  节点编号从1开始
        for(int i = 1; i <= N; i++){
            if(!visited[i]){
                System.out.println(-1);
                return;
            }
        }
        System.out.println(1);
    }
}

写法二:DFS 处理下一个要访问的节点

import java.util.*;

public class Main {

    // DFS方法:处理下一个要访问的节点
    public static void dfs(List<List<Integer>> graph, int key, boolean[] visited) {
        List<Integer> keys = graph.get(key);
        for (int nextKey : keys) {
            if (!visited[nextKey]) {  // 确认下一个是没访问过的节点
                visited[nextKey] = true;
                dfs(graph, nextKey, visited);
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();  // 节点数
        int m = sc.nextInt();  // 边数

        List<List<Integer>> graph = new ArrayList<>(n + 1);
        for (int i = 0; i <= n; i++) {
            graph.add(new ArrayList<>());
        }

        for (int i = 0; i < m; i++) {
            int s = sc.nextInt();
            int t = sc.nextInt();
            graph.get(s).add(t);
        }

        boolean[] visited = new boolean[n + 1];
        visited[1] = true;  // 节点1 预先处理
        dfs(graph, 1, visited);

        // 检查是否都访问到了
        for (int i = 1; i <= n; i++) {
            if (!visited[i]) {
                System.out.println(-1);
                return;
            }
        }
        System.out.println(1);
    }
}

106. 岛屿的周长

简单题,避免大家惯性思维,建议大家先独立做题。
文章讲解

思路

解法一

遍历每一个空格,遇到岛屿(即值为 1)时,计算其上下左右四个方向的情况。如果在这些方向上有水域(即值为 0),或者超出了网格的边界,则认为这是岛屿的一条边,并增加边的计数。

image.png
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[][] grid = new int[n][m];
        
        // 输入网格
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                grid[i][j] = scanner.nextInt();
            }
        }
        
        int[][] direction = {{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 x = i + direction[k][0];
                        int y = j + direction[k][1];
                        
                        // 判断边界和水域
                        if (x < 0 || x >= n || y < 0 || y >= m || grid[x][y] == 0) {
                            result++;
                        }
                    }
                }
            }
        }
        
        System.out.println(result);
    }
}
解法二

首先,计算出总的岛屿数量,然后假设每个岛屿的初始边数是 4,即总边数为 岛屿数量 * 4。然后,计算相邻岛屿的数量,因为每对相邻的岛屿共用一条边,因此每对相邻的岛屿会减少 2 条边。

image.png
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[][] grid = new int[n][m];
        
        // 输入网格
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                grid[i][j] = scanner.nextInt();
            }
        }
        
        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++;
                }
                
            }
        }
        int result = sum * 4 - cover * 2;
        System.out.println(result);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容