【算法面试题汇总】LeetBook列表的算法面试题汇总---图论题目及答案

整理不易留个小心心呗🥰
如果有更好的或者是我有错的地方还请各位大佬指出哦
有些是copy的还望不要介意

单词接龙

题目描述:
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> … -> sk:
每一对相邻的单词只差一个字母。
对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
sk == endWord
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。

示例:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
  • 广度优先遍历

    在这里插入图片描述

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        //将字典存放在哈希表中,便于查询某个单词是否存在
        Set<String> wordSet = new HashSet<>(wordList);
        if(wordSet.size() == 0 || !wordSet.contains(endWord)){
            return 0;
        }
        wordSet.remove(beginWord);

        //广度优先遍历,利用队列以及存储是否访问过的哈希表
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);
        Set<String> visited = new HashSet<>();
        visited.add(beginWord);

        //起点步数为1
        int step = 1;
        while(!queue.isEmpty()){
            int currentSize = queue.size();
            //遍历当前队列的元素
            for(int i=0;i<currentSize;i++){
                String currentWord = queue.poll();
                //如果currentWord修改一个字符后与endWord相同,则返回步数+1
                if(changeWordEveryOneLetter(currentWord,endWord,queue,visited,wordSet)){
                    return step+1;
                }
            }
            step++;
        }
        return 0;
    }

    /**
    *修改每个字符,是否跟endWord匹配
     */
     private boolean changeWordEveryOneLetter(String currentWord,String endWord,Queue<String> queue,Set<String> visited,Set<String> wordSet){
         char[] charArray = currentWord.toCharArray();
         for(int i=0;i<endWord.length();i++){
             //先保存,稍后恢复
             char originChar = charArray[i];
             for(char k='a';k<='z';k++){
                 //更换字符后查看字典中是否存在
                if(k == originChar){
                    continue;
                }
                charArray[i] = k;
                String nextWord = String.valueOf(charArray);
                if(wordSet.contains(nextWord)){
                    if(nextWord.equals(endWord)){
                        return true;
                    }
                    if(!visited.contains(nextWord)){
                        queue.add(nextWord);
                        //标记访问过
                        visited.add(nextWord);
                    }
                }
             }
             charArray[i] = originChar;
         }
         return false;
     }
}
  • 双向广度优先遍历
class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        //将字典存放在哈希表中,便于查询某个单词是否存在
        Set<String> wordSet = new HashSet<>(wordList);
        if(wordSet.size() == 0 || !wordSet.contains(endWord)){
            return 0;
        }

        //将已经访问过的元素放入visited
        Set<String> visited = new HashSet<>();
        //分别用左边和右边扩散的哈希表代替单向BFS里的队列
        Set<String> beginVisited = new HashSet<>();
        beginVisited.add(beginWord);
        Set<String> endVisited = new HashSet<>();
        endVisited.add(endWord);

        int step = 1;
        while(!beginVisited.isEmpty() && !endVisited.isEmpty()){
            //优先选择少的哈希表进行扩散,考虑到的情况更少
            if(beginVisited.size() > endVisited.size()){
                Set<String> temp = beginVisited;
                beginVisited = endVisited;
                endVisited = temp;
            }

            //保证beginVisited是相对较少元素的集合,nextLevelVisited扩散完后,会成为新的beginVisited
            Set<String> nextLevelVisited = new HashSet<>();
            for(String word:beginVisited){
                if(changeWordEveryOneLetter(word, endVisited, visited, wordSet, nextLevelVisited)){
                    return step+1;
                }
            }
            beginVisited = nextLevelVisited;
            step++;
        }
        return 0;
    }

    /**
    *修改每个字符,是否跟endWord匹配
     */
     private boolean changeWordEveryOneLetter(String word,Set<String> endVisited,Set<String> visited,Set<String> wordSet,Set<String> nextLevelVisited){

         char[] charArray = word.toCharArray();
         for(int i=0;i<word.length();i++){
             char orginChar = charArray[i];
             for(char k='a';k<='z';k++){
                 if(orginChar==k){
                     continue;
                 }
                 charArray[i] = k;
                 String nextWord = String.valueOf(charArray);
                 if(wordSet.contains(nextWord)){
                     if(endVisited.contains(nextWord)){
                         return true;
                     }
                     if(!visited.contains(nextWord)){
                         nextLevelVisited.add(nextWord);
                         visited.add(nextWord);
                     }
                 }
             }
             charArray[i] = orginChar;
         }
         return false;
     }
}

岛屿数量

题目描述:
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

示例:

输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3
  • dfs

    • 该题的图dfs可以类比二叉树的dfs
void traverse(TreeNode root) {
    // 判断 base case
    if (root == null) {
        return;
    }
    // 访问两个相邻结点:左子结点、右子结点
    traverse(root.left);
    traverse(root.right);
}

二叉树的dfs两个要素:访问相邻节点和判断base case
对于图来说相邻节点便是上下左右四个格子
而base case便是超出网格范围

class Solution {
    public int numIslands(char[][] grid) {
        int count = 0;
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j]=='1'){
                    dfs(grid,i,j);
                    count++;
                }
            }
        }
        return count;
    }

    public void dfs(char[][] grid,int i,int j){
        if(i<0||j<0||i>=grid.length||j>=grid[0].length||grid[i][j]=='0' || grid[i][j]=='2'){
            return;
        }
        //遍历过的陆地标记为2,也可以直接为0
        grid[i][j]='2';
        dfs(grid,i,j-1);
        dfs(grid,i,j+1);
        dfs(grid,i-1,j);
        dfs(grid,i+1,j);
        //这样的顺序会稍快一丢丢
        //dfs(grid,i+1,j);
        //dfs(grid,i-1,j);
        //dfs(grid,i,j-1);
        //dfs(grid,i,j+1);
    }
}
  • bfs

    如果是陆地则加入队列,进行广度优先搜索,直到队列为空则遍历完此岛屿

class Solution {
    public int numIslands(char[][] grid) {
        int count = 0;
        for(int i = 0; i < grid.length; i++) {
            for(int j = 0; j < grid[0].length; j++) {
                if(grid[i][j] == '1'){
                    bfs(grid, i, j);
                    count++;
                }
            }
        }
        return count;
    }
    private void bfs(char[][] grid, int i, int j){
        Queue<int[]> list = new LinkedList<>();
        list.add(new int[] { i, j });
        while(!list.isEmpty()){
            int[] cur = list.remove();
            i = cur[0]; j = cur[1];
            if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') {
                grid[i][j] = '0';
                list.add(new int[] { i + 1, j });
                list.add(new int[] { i - 1, j });
                list.add(new int[] { i, j + 1 });
                list.add(new int[] { i, j - 1 });
            }
        }
    }
}

课程表

题目描述:
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

示例:

输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。这是可能的。
  • 深度优先

    先寻找拓扑排序的最后节点
    将每一门课看成一个节点,在学习1前要学习0,则从0出发指向1的一条有向边
    若存在环则代表不能完成

class Solution {
    List<List<Integer>> edges;
    int[] visited;
    boolean valid = true;
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        edges = new ArrayList<List<Integer>>();
        for(int i=0;i<numCourses;i++){
            edges.add(new ArrayList<Integer>());
        }
        visited = new int[numCourses];
        for(int[] info:prerequisites){
            edges.get(info[1]).add(info[0]);
        }
        for(int i=0;i<numCourses && valid;i++){
            //未搜索的科目进行深搜
            if(visited[i]==0){
                dfs(i);
            }
        }
        return valid;
    }
    private void dfs(int u){
        //将该科目标记为搜索中
        visited[u] = 1;
        //遍历该科目指向的所有课程
        for(int v:edges.get(u)){
            //未搜索过的科目进行深搜
            if(visited[v]==0){
                dfs(v);
                if(!valid){
                    return;
                }
            }else if(visited[v]==1){
                //遍历的科目中存在搜索中的科目则表示有环
                valid = false;
                return;
            }
        }
        //标记为搜索完
        visited[u]=2;
    }

}
  • 广度优先

    先寻找拓扑排序中最前面的节点
    先将入度为0的存入栈,然后找到该节点指向的节点,并将它的入度-1,若此时它的入度为0则存入栈
    计算存入栈的元素个数,若等于课程数则表示可以,若不等于则表示存在环

class Solution {
    List<List<Integer>> edges;
    int[]  indeg; //存放入度数
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        edges = new ArrayList<List<Integer>>();
        for(int i=0;i<numCourses;i++){
            edges.add(new ArrayList<>());
        }
        indeg = new int[numCourses];
        for(int[] info:prerequisites){
            edges.get(info[1]).add(info[0]);
            ++indeg[info[0]];
        }
        
        //存放入度为0的课程
        Queue<Integer> queue = new LinkedList<>();
        for(int i=0;i<numCourses;i++){
            if(indeg[i]==0){
                queue.offer(i);
            }
        }
        
        //记录有多少个课程存入栈
        int visited=0;
        while(!queue.isEmpty()){
            ++visited;
            int u = queue.poll();
            for(int v:edges.get(u)){
                //入度减1
                --indeg[v];
                if(indeg[v]==0){
                    queue.offer(v);
                }
            }
        }
        return visited==numCourses;
    }
}

课程表Ⅱ

题目描述:
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。
例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。

示例:

输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
  • 深度优先

    先寻找拓扑排序的最后节点
    将每一门课看成一个节点,在学习1前要学习0,则从0出发指向1的一条有向边
    若存在环则代表不能完成

class Solution {
    List<List<Integer>> edges;
    int[] visited;
    boolean valid = true;
    int[] result;
    int index;
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        edges = new ArrayList<List<Integer>>();
        for(int i=0;i<numCourses;i++){
            edges.add(new ArrayList<Integer>());
        }
        visited = new int[numCourses];
        result = new int[numCourses];
        //从拓扑
        index = numCourses-1;
        for(int[] info:prerequisites){
            edges.get(info[1]).add(info[0]);
        }
        for(int i=0;i<numCourses && valid;i++){
            //未搜索的科目进行深搜
            if(visited[i]==0){
                dfs(i);
            }
        }
        if(!valid){
            return new int[0];
        }
        return result;
    }
    private void dfs(int u){
        //将该科目标记为搜索中
        visited[u] = 1;
        //遍历该科目指向的所有课程
        for(int v:edges.get(u)){
            //未搜索过的科目进行深搜
            if(visited[v]==0){
                dfs(v);
                if(!valid){
                    return;
                }
            }else if(visited[v]==1){
                //遍历的科目中存在搜索中的科目则表示有环
                valid = false;
                return;
            }
        }
        //标记为搜索完
        visited[u]=2;
        result[index--]=u;
    }
}
  • 广度优先

    先寻找拓扑排序中最前面的节点
    先将入度为0的存入栈,然后找到该节点指向的节点,并将它的入度-1,若此时它的入度为0则存入栈
    计算存入栈的元素个数,若等于课程数则表示可以,若不等于则表示存在环

class Solution {
    List<List<Integer>> edges;
    int[] indeg; //存放入度数
    int[] result;
    int index;
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        edges = new ArrayList<List<Integer>>();
        for(int i=0;i<numCourses;i++){
            edges.add(new ArrayList<>());
        }
        indeg = new int[numCourses];
        result = new int[numCourses];
        index = 0;
        for(int[] info:prerequisites){
            edges.get(info[1]).add(info[0]);
            ++indeg[info[0]];
        }
        
        //存放入度为0的课程
        Queue<Integer> queue = new LinkedList<>();
        for(int i=0;i<numCourses;i++){
            if(indeg[i]==0){
                queue.offer(i);
            }
        }
        
        //记录有多少个课程存入栈
        int visited=0;
        while(!queue.isEmpty()){
            ++visited;
            int u = queue.poll();
            result[index++]=u;
            for(int v:edges.get(u)){
                //入度减1
                --indeg[v];
                if(indeg[v]==0){
                    queue.offer(v);
                }
            }
        }
        if(index!=numCourses){
            return new int[0];
        }
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值