leetcode(图论专题)

1.导论

  • 图:有向图和无向图
  • 解题步骤:

(1)建图:建什么样的图,怎么建图
(2)然后具体问题具体分析,比如使用BFS、贪心算法等对图进行操作

  • 并查集
//并查集(管理一系列不想交的集合),具有查询和合并的功能
class DisjointSetUnion {
    int[] f;//存储每个元素的根节点
    int[] rank;//记录每个根节点对应树的深度
    int n;

    public DisjointSetUnion(int n) {
        this.n = n;
        this.rank = new int[n];//记录每个根节点对应树的深度
        this.f = new int[n];//存储每个元素的父节点
        for (int i = 0; i < n; i++) {
            this.rank[i] = 1;//初始值设为1
            this.f[i] = i;//一开始将父节点设为自己
        }
    }

    //压缩路径查询方法,递归实现
    //一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可
    public int find(int x) {
        return f[x] == x ? x : (f[x] = find(f[x]));
    }

    //合并方法
    public boolean unionSet(int x, int y) {
        int fx = find(x), fy = find(y);//找到两个结点的根节点
        if (fx == fy) {//两个结点的根节点相同不用合并
            return false;
        }
        if (rank[fx] < rank[fy]) {//以fx为根节点的树的深度小于以fy为根节点的树的深度
            f[fx] = fy;
        } else if (rank[fx] > rank[fy]) {
            f[fy] = fx;
        } else {
            f[fx] = fy;
            rank[fy] += 1;
        }
        return true;
    }
}

2.编程题

2.1 127. 单词接龙

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:

序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。

1 <= beginWord.length <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord、endWord 和 wordList[i] 由小写英文字母组成
beginWord != endWord
wordList 中的所有字符串 互不相同

  • 创建图
    在这里插入图片描述
/*
	1.给每个单词分配一个Id,使用hashmap来存储
	2.建图
	3.广度优先搜索
*/
class Solution {
    Map<String,Integer> wordId = new HashMap<>();//保存单词的Id
    List<List<Integer>> edge = new ArrayList<>();//保存图的边
    int nodeNum = 0;//单词id
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {

        //创建图
        for(String word : wordList){
            addEdge(word);
        }
        addEdge(beginWord);
        
        //不存在
        if(!wordId.containsKey(endWord)) return 0;

        int[] dis = new int[nodeNum];//起始点到每个结点的距离
        Arrays.fill(dis,Integer.MAX_VALUE);//初始化为最大值,搜索的时候如果当前值为最大值,说明此结点还没有访问过
        int beginId = wordId.get(beginWord);
        int endId = wordId.get(endWord);
        dis[beginId] = 0;//起始点的距离为0

        //广度优先搜索
        Queue<Integer> que = new LinkedList<>();
        que.offer(beginId);
        while(!que.isEmpty()){
            int cur = que.poll();
            if(cur == endId){
                return dis[endId] / 2 + 1;
                /*
                    因为添加了虚拟节点,所以我们得到的距离为实际最短路径长度的两倍。
                    同时我们并未计算起点对答案的贡献,所以我们应当返回距离的一半再加一的结果。
                */
            }
            for(int curNext : edge.get(cur)){
                if(dis[curNext] == Integer.MAX_VALUE){
                    dis[curNext] = dis[cur] + 1;
                    que.offer(curNext);
                }
            }
        }
        return 0;
    }

    //创建边    
    //比如hot 连接 : *ot、h*t、ho*
    private void addEdge(String word){
        addWord(word);
        int id1 = wordId.get(word);
        char[] array = word.toCharArray();
        int length = array.length;
        for(int i=0;i<length;++i){
            char temp = array[i];
            array[i] = '*';
            String newWord = new String(array);
            addWord(newWord);
            int id2 = wordId.get(newWord);
            edge.get(id1).add(id2);
            edge.get(id2).add(id1);
            array[i] = temp;
        }
    }

    //添加单词id
    private void addWord(String word){
        if(!wordId.containsKey(word)){
            wordId.put(word,nodeNum++);
            edge.add(new ArrayList<Integer>());
        }
    }
}
  • 优化:双向广度优先搜索
class Solution {
    Map<String, Integer> wordId = new HashMap<String, Integer>();
    List<List<Integer>> edge = new ArrayList<List<Integer>>();
    int nodeNum = 0;

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        for (String word : wordList) {
            addEdge(word);
        }
        addEdge(beginWord);
        if (!wordId.containsKey(endWord)) {
            return 0;
        }

        int[] disBegin = new int[nodeNum];
        Arrays.fill(disBegin, Integer.MAX_VALUE);
        int beginId = wordId.get(beginWord);
        disBegin[beginId] = 0;
        Queue<Integer> queBegin = new LinkedList<Integer>();
        queBegin.offer(beginId);
        
        int[] disEnd = new int[nodeNum];
        Arrays.fill(disEnd, Integer.MAX_VALUE);
        int endId = wordId.get(endWord);
        disEnd[endId] = 0;
        Queue<Integer> queEnd = new LinkedList<Integer>();
        queEnd.offer(endId);

        while (!queBegin.isEmpty() && !queEnd.isEmpty()) {
            int queBeginSize = queBegin.size();
            for (int i = 0; i < queBeginSize; ++i) {
                int nodeBegin = queBegin.poll();
                if (disEnd[nodeBegin] != Integer.MAX_VALUE) {
                    return (disBegin[nodeBegin] + disEnd[nodeBegin]) / 2 + 1;
                }
                for (int it : edge.get(nodeBegin)) {
                    if (disBegin[it] == Integer.MAX_VALUE) {
                        disBegin[it] = disBegin[nodeBegin] + 1;
                        queBegin.offer(it);
                    }
                }
            }

            int queEndSize = queEnd.size();
            for (int i = 0; i < queEndSize; ++i) {
                int nodeEnd = queEnd.poll();
                if (disBegin[nodeEnd] != Integer.MAX_VALUE) {
                    return (disBegin[nodeEnd] + disEnd[nodeEnd]) / 2 + 1;
                }
                for (int it : edge.get(nodeEnd)) {
                    if (disEnd[it] == Integer.MAX_VALUE) {
                        disEnd[it] = disEnd[nodeEnd] + 1;
                        queEnd.offer(it);
                    }
                }
            }
        }
        return 0;
    }

    public void addEdge(String word) {
        addWord(word);
        int id1 = wordId.get(word);
        char[] array = word.toCharArray();
        int length = array.length;
        for (int i = 0; i < length; ++i) {
            char tmp = array[i];
            array[i] = '*';
            String newWord = new String(array);
            addWord(newWord);
            int id2 = wordId.get(newWord);
            edge.get(id1).add(id2);
            edge.get(id2).add(id1);
            array[i] = tmp;
        }
    }

    public void addWord(String word) {
        if (!wordId.containsKey(word)) {
            wordId.put(word, nodeNum++);
            edge.add(new ArrayList<Integer>());
        }
    }
}


2.2 207. 课程表

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。

例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

1 <= numCourses <= 105
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i] 中的所有课程对 互不相同

//拓扑排序
class Solution {
    List<List<Integer>> edges = new ArrayList<>();//存储有向图
    int[] indeg;//存储当前结点的入度值,即当前结点父节点的个数
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // 建图
        // 共创建了与课程个数相等的列表数
        // 每个列表的下标对应的就是每个课程
        // 每个列表里面存储的值,就是该课程指向的下一个课程
        for (int i = 0; i< numCourses; ++i) {
            edges.add(new ArrayList<Integer>());
        }

        // 创建入度表
        // 数组下标对应的是相应的课程,里面的值是该课程的入度值
        indeg = new int[numCourses];
        for (int[] prerequisite : prerequisites) {
            edges.get(prerequisite[1]).add(prerequisite[0]);
            ++indeg[prerequisite[0]];
        }

        // 广度优先搜索
        Queue<Integer> que = new LinkedList<>();
        // 入度值为0的结点先入队列,也就是学习了这些课程才能学习接下来的课程
        for (int i = 0; i < numCourses; ++i) {
            if (indeg[i] == 0){
                que.offer(i);
            }
        }
        int visited = 0;//记录已访问结点的个数
        while (!que.isEmpty()) {
            ++visited;
            int course = que.poll();//获取入度值为0的结点
            for (int courseNext : edges.get(course)) {
                --indeg[courseNext];
                if (indeg[courseNext] == 0){//入度值为0加入队列
                    que.offer(courseNext);
                }
            }
        }
        //如果访问结点的个数等与课程数,说明能够修完所有课程
        return visited == numCourses;
    }
}

2.3 210. 课程表 II

现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。

例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。

1 <= numCourses <= 2000
0 <= prerequisites.length <= numCourses * (numCourses - 1)
prerequisites[i].length == 2
0 <= ai, bi < numCourses
ai != bi
所有[ai, bi] 互不相同

//拓扑排序
class Solution {
    List<List<Integer>> edges = new ArrayList<>();//存储有向图
    int[] indeg;//存储当前结点的入度值,即当前结点父节点的数量
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        // 建图
        // 共创建了与课程个数相等的列表数
        // 每个列表的下标对应的就是每个课程
        // 每个列表里面存储的值,就是该课程指向的下一个课程
        for (int i = 0; i< numCourses; ++i) {
            edges.add(new ArrayList<Integer>());
        }

        // 创建入度表
        // 数组下标对应的是相应的课程,里面的值是该课程的入度值
        indeg = new int[numCourses];
        for (int[] prerequisite : prerequisites) {
            edges.get(prerequisite[1]).add(prerequisite[0]);
            ++indeg[prerequisite[0]];
        }

        // 广度优先搜索
        Queue<Integer> que = new LinkedList<>();
        // 入度值为0的结点先入队列,也就是学习了这些课程才能学习接下来的课程
        for (int i = 0; i < numCourses; ++i) {
            if (indeg[i] == 0){
                que.offer(i);
            }
        }
        int[] ans = new int[numCourses];
        int index = 0;
        while (!que.isEmpty()) {
            int course = que.poll();//获取入度值为0的结点
            ans[index++] = course;
            for (int courseNext : edges.get(course)) {
                --indeg[courseNext];
                if (indeg[courseNext] == 0){//入度值为0加入队列
                    que.offer(courseNext);
                }
            }
        }
        if (index != numCourses) {//说明不能学完所有课程
            return new int[0];
        }
        return ans;
    }
}

2.4 399. 除法求值

给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] = [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi = values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。

另有一些以数组 queries 表示的问题,其中 queries[j] = [Cj, Dj] 表示第 j 个问题,请你根据已知条件找出 Cj / Dj = ? 的结果作为答案。

返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案。

注意:输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结

1 <= equations.length <= 20
equations[i].length == 2
1 <= Ai.length, Bi.length <= 5
values.length == equations.length
0.0 < values[i] <= 20.0
1 <= queries.length <= 20
queries[i].length == 2
1 <= Cj.length, Dj.length <= 5
Ai, Bi, Cj, Dj 由小写英文字母与数字组成

/*
	1.字符映射为整数
	2.建图(无向图)
	3.遍历问题,对每个问题进行广度优先搜索
*/
class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        int nvars = 0;//字符映射为的整数
        Map<String,Integer> variables = new HashMap<>();

        int n = equations.size();
        //将equations数组中的不同的字符串映射为整数
        for (int i = 0; i < n; ++i) {
            if (!variables.containsKey(equations.get(i).get(0))) {
                variables.put(equations.get(i).get(0),nvars++);
            }
            if (!variables.containsKey(equations.get(i).get(1))) {
                variables.put(equations.get(i).get(1),nvars++);
            }
        }

        //建图
        List<List<Pair>> edges = new ArrayList<>();//对于每个点,存储其直接连接到的所有点及对应的权值
        for (int i = 0; i < nvars; ++i) {
            edges.add(new ArrayList<Pair>());
        }
        for ( int i = 0; i < n; ++i) {
            int va = variables.get(equations.get(i).get(0));//被除数所映射的整数
            int vb = variables.get(equations.get(i).get(1));//除数所映射的整数
            edges.get(va).add(new Pair(vb,values[i]));
            edges.get(vb).add(new Pair(va,1.0/values[i]));
        }

        int queriesCount = queries.size();
        double[] ret = new double[queriesCount];//存储结果

        //遍历问题进行求解
        for (int i = 0; i < queriesCount; ++i) {
            List<String> query = queries.get(i);
            double result = -1.0;
            if (variables.containsKey(query.get(0)) && variables.containsKey(query.get(1))) {//如果问题中的字符存在于map中
                int ia = variables.get(query.get(0));
                int ib = variables.get(query.get(1));
                if (ia == ib) {//如果被除数和除数相等,商为1.0
                    result = 1.0;
                } else { // 不相等
                    //广度优先搜索
                    Queue<Integer> points = new LinkedList<>();
                    points.offer(ia);
                    double[] ratios = new double[nvars];//存储ia/i的值
                    Arrays.fill(ratios,-1.0);//初始化为-1.0,因为有些答案无法确定
                    ratios[ia] = 1.0;// ia/ia = 1.0

                    while (!points.isEmpty() && ratios[ib] < 0) {
                        int x = points.poll();
                        for (Pair pair : edges.get(x)) {
                            int y = pair.index;
                            double val = pair.value;// 权重,即x/y的值
                            if (ratios[y] < 0) {
                                ratios[y] = ratios[x] * val;// 即 ia/y = (ia/x) * (x/y);
                                points.offer(y);
                            }
                        }
                    }
                    result = ratios[ib];
                }
            }
            ret[i] = result;
        }
        return ret;
    }
}


//带权值结点
class Pair {
    int index;//当前结点的索引
    double value;//权重:父节点索引与当前结点的索引的比值
    Pair(int index,double value) {
        this.index = index;
        this.value = value;
    }
}

2.5 1584. 连接所有点的最小费用

给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。

连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。

请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。
1 <= points.length <= 1000
-106 <= xi, yi <= 106
所有点 (xi, yi) 两两不同。

并查集详解

//Kruskal算法
class Solution {
    public int minCostConnectPoints(int[][] points) {
        int n = points.length;
        DisjointSetUnion dsu = new DisjointSetUnion(n);

        //建图,所有的点进行连接
        List<Edge> edges = new ArrayList<Edge>();
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                edges.add(new Edge(dist(points, i, j), i, j));
            }
        }
        //按照两点之间的距离,从小到大排序,贪心算法:每次选最小边加入
        Collections.sort(edges, new Comparator<Edge>() {
            public int compare(Edge edge1, Edge edge2) {
                return edge1.len - edge2.len;
            }
        });
        int ret = 0, num = 1;
        for (Edge edge : edges) {
            int len = edge.len, x = edge.x, y = edge.y;
            if (dsu.unionSet(x, y)) {
                ret += len;
                num++;
                if (num == n) {
                    break;
                }
            }
        }
        return ret;
    }

    //计算两点间的距离
    public int dist(int[][] points, int x, int y) {
        return Math.abs(points[x][0] - points[y][0]) + Math.abs(points[x][1] - points[y][1]);
    }
}

//并查集(管理一系列不想交的集合),具有查询和合并的功能
class DisjointSetUnion {
    int[] f;//存储每个元素的父节点
    int[] rank;//记录每个根节点对应树的深度
    int n;

    public DisjointSetUnion(int n) {
        this.n = n;
        this.rank = new int[n];//记录每个根节点对应树的深度
        this.f = new int[n];//存储每个元素的父节点
        for (int i = 0; i < n; i++) {
            this.rank[i] = 1;//初始值设为1
            this.f[i] = i;//一开始将父节点设为自己
        }
    }

    //压缩路径查询方法,递归实现
    //一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可
    public int find(int x) {
        return f[x] == x ? x : (f[x] = find(f[x]));
    }

    //合并方法
    public boolean unionSet(int x, int y) {
        int fx = find(x), fy = find(y);//找到两个结点的根节点
        if (fx == fy) {//两个结点的根节点相同不用合并
            return false;
        }
        if (rank[fx] < rank[fy]) {//以fx为根节点的树的深度小于以fy为根节点的树的深度
            f[fx] = fy;
        } else if (rank[fx] > rank[fy]) {
            f[fy] = fx;
        } else {
            f[fx] = fy;
            rank[fy] += 1;
        }
        return true;
    }
}


//边的类
class Edge {
    //len表示x和y两点间的距离
    int len, x, y;

    public Edge(int len, int x, int y) {
        this.len = len;
        this.x = x;
        this.y = y;
    }
}

2.6 1631. 最小体力消耗路径

你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。

一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。

请你返回从左上角走到右下角的最小 体力消耗值 。

rows == heights.length
columns == heights[i].length
1 <= rows, columns <= 100
1 <= heights[i][j] <= 106

class Solution {
    public int minimumEffortPath(int[][] heights) {
        int m = heights.length;
        int n = heights[0].length;
        // 建图
        // 数组中的值为{当前结点头顶/左边结点的编号,当前结点编号,权值}
        List<int[]> edges = new ArrayList<>();
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                int id = i * n + j;//对每个点进行编号
                if (i > 0) {
                    edges.add(new int[]{id-n, id, Math.abs(heights[i][j] - heights[i-1][j])});
                }
                if (j > 0) {
                    edges.add(new int[]{id-1, id, Math.abs(heights[i][j] - heights[i][j-1])});
                }
            }
        }
        //按照权值进行从小到大进行排序
        Collections.sort(edges, (edge1,edge2) -> edge1[2] - edge2[2]);

        DisjointSetUnion dis = new DisjointSetUnion(m*n);
        int ans= 0;//记录最小体力消耗值
        for (int[] edge : edges) {
            int x = edge[0];
            int y = edge[1];
            int v = edge[2];
            dis.unionSet(x,y);
            // 当加入一条权值为v的边后,如果左上角和右下角从非连通状态变为连通状态,那么v即为答案
            if(dis.connected(0,m*n-1)){
                ans = v;
                break;
            }
        }
        return ans;
    }
}

// 并查集
class DisjointSetUnion {
    int[] f;
    int[] rank;
    int n;
    public DisjointSetUnion(int n) {
        this.n = n;
        f = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; ++i) {
            f[i] = i;
            rank[i] = 1;
        }
    }
    public int find(int x) {
        return f[x] == x ? x : (f[x] = find(f[x]));
    }

    public boolean unionSet(int x,int y) {
        int fx = find(x);
        int fy = find(y);
        if(fx == fy) return false;
        if (rank[fx] < rank[fy]) {
            f[fx] = fy;
        } else if (rank[fx] > rank[fy]) {
            f[fy] = fx;
        } else {
            f[fx] = fy;
            rank[fy] += 1;
        }
        return true;
    }

    // 判断两个点是否连接
    public boolean connected (int x,int y) {
        x = find(x);
        y = find(y);
        return x == y;
    }
}

2.7 2101. 引爆最多的炸弹

给你一个炸弹列表。一个炸弹的 爆炸范围 定义为以炸弹为圆心的一个圆。

炸弹用一个下标从 0 开始的二维整数数组 bombs 表示,其中 bombs[i] = [xi, yi, ri] 。xi 和 yi 表示第 i 个炸弹的 X 和 Y 坐标,ri 表示爆炸范围的 半径 。

你需要选择引爆 一个 炸弹。当这个炸弹被引爆时,所有 在它爆炸范围内的炸弹都会被引爆,这些炸弹会进一步将它们爆炸范围内的其他炸弹引爆。

给你数组 bombs ,请你返回在引爆 一个 炸弹的前提下,最多 能引爆的炸弹数目。
1 <= bombs.length <= 100
bombs[i].length == 3
1 <= xi, yi, ri <= 105

class Solution {
    List<List<Integer>> edges;
    int n;
    public int maximumDetonation(int[][] bombs) {
        n = bombs.length;
        //根据引爆关系建图
        edges = new ArrayList<>();
        for (int i = 0; i < n; ++i) {
            edges.add(new ArrayList<Integer>());
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (i !=  j && isConnected(i, j, bombs)) {
                    edges.get(i).add(j);
                }
            }
        }

        //逐个搜索,求出最大值
        int ans = 0;
        for (int i = 0; i < n; ++i) {
           
            ans = Math.max(ans, bfs(i));
        }
        return ans;
    }

    //广度优先搜索,计算该炸弹可以引爆的数目
    public int bfs(int i) {
        int count = 1;
        boolean[] visited = new boolean[n];
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(i);
        visited[i] = true;
        while (!queue.isEmpty()) {
            int index = queue.poll();
            if(edges.get(index).size() != 0) {
                for (int bomb : edges.get(index)) {
                    if (!visited[bomb]) {
                        ++count;
                        queue.offer(bomb);
                        visited[bomb] = true;
                    }
                }
            }
        }
        return count;
    }

    //判断是否连接
    public boolean isConnected(int i, int j, int[][] bombs) {
        long dx = bombs[i][0] - bombs[j][0];
        long dy = bombs[i][1] - bombs[j][1];
        return (long)bombs[i][2] * bombs[i][2] >= dx * dx + dy * dy;
    }
}
// 使用Map建图,加快索引
class Solution {
    Map<Integer,List<Integer>> edges = new HashMap<>();
    int n;
    public int maximumDetonation(int[][] bombs) {
        n = bombs.length;
        //根据引爆关系建图
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (i !=  j && isConnected(i, j, bombs)) {
                    List<Integer> edge = edges.getOrDefault(i,new ArrayList<Integer>());
                    edge.add(j);
                    edges.put(i,edge);
                }
            }
        }

        //逐个搜索,求出最大值
        int ans = 0;
        for (int i = 0; i < n; ++i) {
           
            ans = Math.max(ans, bfs(i));
        }
        return ans;
    }

    //广度优先搜索,计算该炸弹可以引爆的数目
    public int bfs(int i) {
        int count = 1;
        boolean[] visited = new boolean[n];
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(i);
        visited[i] = true;
        while (!queue.isEmpty()) {
            int index = queue.poll();
            if(edges.getOrDefault(index,new ArrayList<Integer>()).size() != 0) {
                for (int bomb : edges.get(index)) {
                    if (!visited[bomb]) {
                        ++count;
                        queue.offer(bomb);
                        visited[bomb] = true;
                    }
                }
            }
        }
        return count;
    }

    //判断是否连接
    public boolean isConnected(int i, int j, int[][] bombs) {
        long dx = bombs[i][0] - bombs[j][0];
        long dy = bombs[i][1] - bombs[j][1];
        return (long)bombs[i][2] * bombs[i][2] >= dx * dx + dy * dy;
    }
}

2.8 DD3 地下迷宫(牛客网)

小青蛙有一天不小心落入了一个地下迷宫,小青蛙希望用自己仅剩的体力值P跳出这个地下迷宫。为了让问题简单,假设这是一个n*m的格子迷宫,迷宫每个位置为0或者1,0代表这个位置有障碍物,小青蛙达到不了这个位置;1代表小青蛙可以达到的位置。小青蛙初始在(0,0)位置,地下迷宫的出口在(0,m-1)(保证这两个位置都是1,并且保证一定有起点到终点可达的路径),小青蛙在迷宫中水平移动一个单位距离需要消耗1点体力值,向上爬一个单位距离需要消耗3个单位的体力值,向下移动不消耗体力值,当小青蛙的体力值等于0的时候还没有到达出口,小青蛙将无法逃离迷宫。现在需要你帮助小青蛙计算出能否用仅剩的体力值跳出迷宫(即达到(0,m-1)位置)。

输入描述:
输入包括n+1行:
第一行为三个整数n,m(3 <= m,n <= 10),P(1 <= P <= 100)
接下来的n行:
每行m个0或者1,以空格分隔

输出描述:
如果能逃离迷宫,则输出一行体力消耗最小的路径,输出格式见样例所示;如果不能逃离迷宫,则输出"Can not escape!"。 测试数据保证答案唯一

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 P = 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();
            }
        }
        
        // 构建邻接矩阵
        int[][] graph = new int[n*m][n*m];
        
        // 初始化:最初默认为不连通,都设置为最大值
        for(int i = 0; i < n*m; ++i) {
            Arrays.fill(graph[i],Integer.MAX_VALUE);
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (i > 0 && grid[i-1][j] == 1) {
                    graph[i * m + j][(i - 1) * m + j] = 3;
                }
                if (i < n - 1 && grid[i + 1][j] == 1) {
                    graph[i * m + j][(i + 1) * m + j] = 0;
                }
                if (j > 0 && grid[i][j - 1] == 1) {
                    graph[i * m + j][i * m + j - 1] = 1;
                }
                if (j < m - 1 && grid[i][j + 1] == 1) {
                    graph[i * m + j][i * m + j + 1] = 1;
                }
            }
        }
        
        int[] prenode = new int[n*m]; // 前驱结点
        int[] mindist = new int[n*m]; // 最短路径
        boolean[] found = new boolean[n*m];// 该节点是否已经找到最短路径
        // 初始化
        for (int i = 0; i < n*m; ++i) {
            prenode[i] = 0;
            mindist[i] = graph[0][i];
        }
        
        found[0] = true;
        for (int i = 1; i < n*m; ++i) {
            int near = 0;// 用来保存最近的结点位置
            int min = Integer.MAX_VALUE;// 用来保存最近的距离
            for (int j = 0; j < n*m; ++j) {
                if (!found[j] && mindist[j]  < min) {
                    min = mindist[j];
                    near = j;
                }
            }
            if (min == Integer.MAX_VALUE) {// 找不到连通路径
                break;
            }
            found[near] = true;
            // 根据找到最近结点near 修正前驱结点和最短距离
            for (int j = 0; j < n*m; ++j) {
                if (!found[j]  && graph[near][j] !=  Integer.MAX_VALUE && (min + graph[near][j] < mindist[j])) {
                    prenode[j] =  near;
                    mindist[j]  = min + graph[near][j];
                }
            }
            
        }
        
        // 输出(0,0)到(0,m-1)的路径
        if (mindist[m - 1] > P) {
            System.out.println("Can not escape!");
        }else {
            Stack<int[]> path = new Stack<>();
            for (int i = m-1; i != 0; i = prenode[i]) {
                path.push(new int[]{i/m,i%m});
            }
            System.out.print("[0,0]");
            while (!path.isEmpty()) {
                int[] t = path.pop();;
                System.out.print(",[" + t[0] + "," +t[1] + "]");
                
            }
        }
        
    }
    
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Listen·Rain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值