Day59_20250207_图论part4_110.字符串接龙|105.有向图的完全可达性|106.岛屿的周长

Day59_20250207_图论part4_110.字符串接龙|105.有向图的完全可达性|106.岛屿的周长

110.字符串接龙

题目

题目描述

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

  1. 序列中第一个字符串是 beginStr。
  2. 序列中最后一个字符串是 endStr。
  3. 每次转换只能改变一个字符。
  4. 转换过程中的中间字符串必须是字典 strList 中的字符串。

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

输入描述

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

输出描述

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

输入示例

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

输出示例

4

提示信息

从 startStr 到 endStr,在 strList 中最短的路径为 abc -> dbc -> dec -> def,所以输出结果为 4

数据范围:

2 <= N <= 500

思路

  • 思路

    • 求最短路径的长度
    • 思考
      • 图中的线是如何连在一起的
        • 如果只差1个字符,就是有连接。
      • 起点和终点的最短路径长度
        • 无向图求最短路,广搜最合适,只要搜到了终点,一定是最短的路径。(起点中心向四周扩散)
      • 无向图,用标记位(节点是否走过),否则是死循环
      • 使用set检查字符串是否出现在字符串集合里更快一些
    • 核心
      • 每次只能改一个字母
      • 改完后必须是wordList里的单词
      • 求最短转换路径(BFS)
  • 代码

    import java.util.*;
    
    public class Main {
        public static void main(String[] args) {
            //输入
            Scanner scanner = new Scanner(System.in);
            int n = scanner.nextInt();//单词列表中可用的单词个数
            scanner.nextLine();//换行
            String beginStr = scanner.next();//起始单词
            String endStr = scanner.next();//目标单词
            scanner.nextLine();//换行
            //初始化存储所有的单词
            List<String> wordList = new ArrayList<>();
            //将2个转换词组加进去
            wordList.add(beginStr);
            wordList.add(endStr);
            //读取n个单词到wordList中
            for (int i = 0; i < n; i++) {
                wordList.add(scanner.nextLine());
            }
            //bfs
            int count = bfs(beginStr, endStr, wordList);
            System.out.println(count);
        }
    
        /**
         * 广度优先搜索-寻找最短路径
         */
        public static int bfs(String beginStr, String endStr, List<String> wordList) {
            int len = 1;//路径的层数,初始路径长度为1
            //1.set:判断单词是否在wordList里[查询]
            Set<String> set = new HashSet<>(wordList);
            //2.防止重复访问的单词集合
            Set<String> visited = new HashSet<>();
            //3.存储bfs层次遍历的队列[beginstr]
            Queue<String> q = new LinkedList<>();
            visited.add(beginStr);
            q.add(beginStr);
            q.add(null);
          
            //4.每次修改1个字母,看看是否变成set里的单词
          
            //6.如果队列为null,还没找到endStr,返回0
          
            //bfs层次遍历
            //区分不同的层次,确保计算的是最短路径的步数。
            //null是层次标记,来标记当前层结束,进入下一层
            while (!q.isEmpty()) {
                String node = q.remove();//取出beginstr头部单词
                //遇到null,这一层结束
                if (node == null) {
                    //if q不为空,还有下一层的单词需要处理,增加len(层数+1)
                    if (!q.isEmpty()) {
                        len++;//进入下一层
                        q.add(null);//在队列尾部加入null作为下一层的标记
                    }
                    continue;//继续下一次循环
                }
              
                //寻找邻接单词
                //改变单词的一个字母,寻找邻接单词,用bfs找出beginstr到endStr的最短路径
                //(1)遍历当前单词node的每个字符
                char[] charArray = node.toCharArray();
                //(2)寻找邻接单词
                for (int i = 0; i < charArray.length; i++) {
                    //记录旧值,用于回滚修改
                    char old = charArray[i];
                    //(3)改变node的每个字符,用a-z26个字母替换charArray[i],生成新单词newWord
                    for (char j = 'a'; j <= 'z'; j++) {
                        charArray[i] = j;
                        String newWord = new String(charArray);
                        //(4)检查newWord是否在wordList里
                        //如果newWord在set里,并且没被访问过
                        if (set.contains(newWord) && !visited.contains(newWord)) {
                            q.add(newWord);//加入队列,等待下一轮bfs
                            visited.add(newWord);//记录访问,防止重复
                            //找到结尾
                            //(5)如果newWord是endStr,返回len+1最短路径长度
                            if (newWord.equals(endStr)) return len + 1;
                        }
                    }
                    //4.恢复原始单词
                    charArray[i] = old;
                }
            }
            return 0;
        }
    }
    

总结

  • 这题比较抽象。

105.有向图的完全可达性

题目

【题目描述】

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

【输入描述】

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

【输出描述】

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

【输入示例】

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

【输出示例】

1

【提示信息】

从 1 号节点可以到达任意节点,输出 1。

数据范围:

  • 1 <= N <= 100;
  • 1 <= K <= 2000。

思路

  • 思路

    • 有向图搜索全路径,只能用深搜(dfs)和广搜(bfs)来搜

    • 深搜三部曲

      • 确认递归函数和参数

        • key当前得到的可以
        • visited 记录访问过的房间
      • 确认终止条件

        • 处理当前访问的节点还是处理下一个要访问的节点,决定终止条件怎么写

        • 什么是处理?

          • visited数组来记录访问过的节点,默认数组是false,把元素标记为true是处理本节点了。
          • 如果遇到true,终止本层递归,如果是false,处理本层递归的节点。
        • 2种情况

          • 处理当前节点,有终止条件

            // 写法一:处理当前访问的节点
            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);
                    }
                }
            }
            
      • 处理目前搜索节点出发的路径

        • 为什么没有回溯?
          • 只需要判断1节点是否能到所有节点,没有必要回溯去撤销操作了,只要遍历过的节点一律都标记上。
  • 核心思路

    • dfs

      • dfs只访问可以到达的节点(visited[i]==true) 说明1可以到达i+1号节点
      • 终止条件:是否全为true。
      • 只遍历可达节点
    • bfs

  • 代码1 dfs

    import java.util.*;
    public class Main{
        //1.构建邻接表adjList(图的结构)
        public static List<List<Integer>> adjList=new ArrayList<>();
        public static void main(String[] agrs){
            //输入
            Scanner sc=new Scanner(System.in);
            int vertices_num=sc.nextInt();
            int line_num=sc.nextInt();
            //装载到LinkedList
            for(int i=0;i<vertices_num;i++){
                adjList.add(new LinkedList<>());
            }
            //初始化
            for(int i=0;i<line_num;i++){
                int s=sc.nextInt();
                int t=sc.nextInt();
                adjList.get(s-1).add(t-1);
            }
            //visited数组来记录访问过的节点是否true和false
    	//dfs
            boolean[] visited=new boolean[vertices_num];
            dfs(visited,0);
            //打印结果
            for(int i=0;i<vertices_num;i++){
                if(!visited[i]){//没有访问过,输出false
                    System.out.println(-1);
                    return;
                }
            }
    	//访问过,输出true
            System.out.println(1);
        }
        //2.使用dfs和bfs遍历从1节点出发能到达的所有节点
        public static void dfs(boolean[] visited,int key){
            //3.终止条件
            if(visited[key]){
                return;
            }
            visited[key]=true;
            List<Integer> nextKeys=adjList.get(key);
            for(int nextKey:nextKeys){
                dfs(visited,nextKey);
            }
        }
    }
    
  • 代码2

    import java.util.*;
    public class Main{
        //1.构建邻接表adjList(图的结构)
        public static List<List<Integer>> adjList=new ArrayList<>();
      
        public static void main(String[] agrs){
            //输入
            Scanner sc=new Scanner(System.in);
            int vertices_num=sc.nextInt();
            int line_num=sc.nextInt();
            //装载到LinkedList
            for(int i=0;i<vertices_num;i++){
                adjList.add(new LinkedList<>());
            }
            //初始化
            for(int i=0;i<line_num;i++){
                int s=sc.nextInt();
                int t=sc.nextInt();
                adjList.get(s-1).add(t-1);
            }
            //构造邻接表
            boolean[] visited=new boolean[vertices_num];
            //dfs
            //dfs(visited,0);
            //bfs 
            bfs(visited,0);
        
            //打印结果
            for(int i=0;i<vertices_num;i++){
                if(!visited[i]){
                    System.out.println(-1);
                    return;
                }
            }
            System.out.println(1);
        
        
        
        
       
        }
        //2.使用dfs和bfs遍历从1节点出发能到达的所有节点
        public static void dfs(boolean[] visited,int key){
            //3.终止条件
            if(visited[key]){
                return;
            }
            visited[key]=true;
            List<Integer> nextKeys=adjList.get(key);//从1号节点(索引0)开始
            for(int nextKey:nextKeys){
                dfs(visited,nextKey);
            }
        }
        //3.bfs  
        public static void bfs(boolean[] visited,int key){
            Queue<Integer> queue=new LinkedList<Integer>();
            queue.add(key);//从1号节点索引0开始
            visited[key]=true;//标记起点已访问
            while(!queue.isEmpty()){
                int curKey=queue.poll();//取出当前节点
                List<Integer> list=adjList.get(curKey);//获得邻接点
                for(int nextKey:list){
                    //只访问未访问的节点
                    if(!visited[nextKey]){
                        queue.add(nextKey);//加入队列,等到下一轮遍历
                        visited[nextKey]=true;//标记已访问
                    }
                }
            }
        
        }
      
    }
    

总结

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
提示信息

岛屿的周长为 14。

数据范围:

1 <= M, N <= 50。

思路

  • 思路

    • 遇到1时,默认贡献4条边
    • 如果1的上下左右有相邻的1,就减少2条边
    • 最终求得岛屿的总周长
  • 代码

    import java.util.*;
    
    public class Main {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int N = sc.nextInt();
            int M = sc.nextInt();
            int[][] grid = new int[N][M];
    
            // 读取输入
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < M; j++) {
                    grid[i][j] = sc.nextInt();
                }
            }
    
            // 计算岛屿周长
            System.out.println(islandPerimeter(grid));
        }
    
        public static int islandPerimeter(int[][] grid) {
            int perimeter = 0;
            int rows = grid.length, cols = grid[0].length;
    
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    if (grid[i][j] == 1) {
                        perimeter += 4; // 每个陆地默认贡献4条边
                        // 上方有陆地
                        if (i > 0 && grid[i - 1][j] == 1) perimeter -= 2;
                        // 左方有陆地
                        if (j > 0 && grid[i][j - 1] == 1) perimeter -= 2;
                    }
                }
            }
    
            return perimeter;
        }
    }
    

总结

  • 避免惯性思维影响自己做题。
相关推荐
陈老师还在写代码1 分钟前
安卓开发用Java、Flutter、Kotlin的区别
android·java·flutter
gentle_ice2 分钟前
搜索二维矩阵——巧用右上角起点搜索法,高效解决二维矩阵查找问题
数据结构·算法·leetcode·矩阵
鲤籽鲲10 分钟前
C# ManualResetEvent 类 使用详解
java·开发语言·c#·多线程
赵璘婳11 分钟前
Perl语言的云计算
开发语言·后端·golang
加油,旭杏34 分钟前
【C++语言】C++入门
开发语言·c++
硬件人某某某34 分钟前
微信小程序~电器维修系统小程序
java·ajax·微信小程序·小程序
猿java37 分钟前
MySQL 如何实现主从复制?
java·后端·mysql
小高Baby@42 分钟前
Deepseek
人工智能·笔记
martian6651 小时前
【Java基础篇】——第4篇:Java常用类库与工具类
java·开发语言
violin-wang1 小时前
Intellij IDEA调整栈内存空间大小详细教程,添加参数-Xss....
java·ide·intellij-idea·xss·栈内存·栈空间