力扣-图论
-
深度优先搜索
-
剑指 Offer II 111. 计算除法
我的题解:
**思路:*字符串a / b = 2.0 , b / c = 3.0 可以求: b / c = 3.0, c / b = 1.0 / 3.0, 因此我们可以将a / b描述为从a到b的一条边,这样就抽象画出一张图,我们将所有的点与权值加入图中,然后遍历queries首先看这两个字符是否包含在图中若没有则不需要搜索,直接将该点赋为-1.0,若两个点都在图中,且都是同一个点,那么自己到自己,将该点赋为1.0,其它情况我们进行深度优先搜索对图进行遍历,我们每访问一个节点就对该节点进行标记,去搜索与该节点连接的节点,若是目标节点就直接返回,若找到目标节点,即该路径存在就返回该路径的值 * 当前路径的值
class Solution { //存储该图 Map<String, Map<String, Double>> map = new HashMap<>(); //标记该字符是否被访问 public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) { /** * 构建图 */ for(int i = 0; i < values.length; i++){ //本别代表着两个字符 String a = equations.get(i).get(0); String b = equations.get(i).get(1); double k = values[i]; //若这两个字符未在图中就创建 if(!map.containsKey(a)) map.put(a, new HashMap<>()); if(!map.containsKey(b)) map.put(b, new HashMap<>()); //将值存入map中代表路径的权值 map.get(a).put(b, k); map.get(b).put(a, 1.0 / k); } /** * 遍历queries并搜索答案 */ double[] ans = new double[queries.size()]; for(int i = 0; i < queries.size(); i++){ String a = queries.get(i).get(0); String b = queries.get(i).get(1); //若a或b不存在map中 if(!map.containsKey(a) || !map.containsKey(b)) ans[i] = -1.0d; else if(a.equals(b)) ans[i] = 1.0d; else ans[i] = dfs(a, b, new HashSet<>()); } return ans; } //搜索的边界条件就是visited是否已访问 public double dfs(String a, String b, Set<String> visited){ //标记当前点为已访问 visited.add(a); //边界条件-边 //划分 for(Map.Entry<String, Double> entry : map.get(a).entrySet()){ //元素-值 String e = entry.getKey(); double val = entry.getValue(); //判断 //1.若等于目标b则直接返回 //字符串相等不能用双等号 if(e.equals(b)) return val; //继续向下 if(!visited.contains(e)){ double tmp = dfs(e, b, visited); //判断,若不存在则为-1,存在则不为-1 if(tmp != -1) //返回 return tmp * val; } } //未找到 return -1.0; } }
-
547. 省份数量
我的题解:
思路: 深度优先搜索,我们对矩阵的每一行进行遍历,每一行代表一个点,从该点开始进行深度搜索,找联通的分量若该联通分量未访问,就继续向下搜索,同时给该点标记为已访问,我们只需要看该行的点有没有被访问就知道该点是不是与其他点联通,若未访问则计数器+1
public int findCircleNum(int[][] isConnected) { int n = isConnected.length; boolean[] isVisited = new boolean[n]; int count = 0; for (int i = 0; i < n; i++) { if(!isVisited[i]){ isVisited[i] = true; dfs(isConnected, isVisited, i); count++; } } return count; } public void dfs(int[][] isConnected, boolean[] isVisited, int i){ //边界条件是isVisited for (int j = 0; j < isVisited.length; j++) { //若该点联通且,未访问 if(isConnected[i][j] == 1 && !isVisited[j]){ isVisited[j] = true; //向下搜索 dfs(isConnected, isVisited, j); } } }
-
由斜杠划分区域
我的题解:
思路: 我们可以将该题转化为求岛屿的个数,对于每一块方格我们将它分成9小块,然后根据斜杠的种类去填充1,这样子就把上述问题转化成了求岛屿个数的问题,岛屿对应的是0,我们这里用深度优先搜索向四面扩展,将为0的数变成1
/* * 思路:我们可以将该题转化为求岛屿的个数,对于每一块方格我们将它分割成9小块,我们根据斜杠的种类去填充这些小块,这样子就把上述问题转化尾了求岛屿的个数 * 求岛屿的个数我们可以用深度优先搜索或者广度优先搜索 * */ int[][] arr; int count; int[] fx = {0, 0, -1, 1}; int[] fy = {-1, 1, 0, 0}; int n; public int regionsBySlashes(String[] grid) { n = grid.length; arr = new int[3 * n][3 * n]; count = 0; for (int i = 0; i < n; i++) { char[] chars = grid[i].toCharArray(); for (int j = 0; j < n; j++) { if(chars[j] == '/'){ arr[i * 3 + 2][j * 3] = arr[i * 3 + 1][j * 3 + 1] = arr[i * 3][j * 3 + 2] = 1; }else if(chars[j] == '\\'){ arr[i * 3][j * 3] = arr[i * 3 + 1][j * 3 + 1] = arr[i * 3 + 2][j * 3 + 2] = 1; } } } for (int i = 0; i < 3 * n; i++) { for (int j = 0; j < 3 * n; j++) { if(arr[i][j] == 0){ dfs(i, j); count++; } } } return count; } //深度优先 public void dfs(int i, int j){ arr[i][j] = 1; for (int k = 0; k < 4; k++) { int newX = i + fx[k]; int newY = j + fy[k]; //若合法就继续搜索 if(newX >= 0 && newX < 3 * n && newY >= 0 && newY < 3 * n && arr[newX][newY] == 0){ dfs(newX, newY); } } }
-
水位上升的泳池中游泳
我的题解:
思路: 二分查找-深度优先搜索,由题意可知,因为平台的高度是连续的从0~n*n-1。因此如果存在可行的路径的话,那么在这些高度中总会有一个高度满足
能从起点走到终点
1.初始化left = 0, right = n * n - 1,边界条件 left < right,初始化ans = 0
2.每次看中间位置mid = (left + right) / 2,是否能用深度优先搜索,从头结点走到尾结点,若是则更新right = mid,否则left = mid + 1
3.返回结果leftclass Solution { /** * @param grid * @return */ int[] fx = {0, 0, -1, 1}, fy = {-1, 1, 0, 0}; int n; int[][] grid; public int swimInWater(int[][] grid) { //step1 n = grid.length; this.grid = grid; int left = 0, right = n * n - 1; //int ans = 0 //step2 while(left < right){ int mid = (left + right) >> 1; if(grid[0][0] <= mid && dfs(mid, 0, 0, new boolean[n][n])){ //ans = mid; //right = mid - 1; right = mid; }else{ left = mid + 1; } } return left; } public boolean dfs(int mid, int x, int y, boolean[][] isVisited){ isVisited[x][y] = true; if(x == n - 1 && y == n - 1) return true; for (int i = 0; i < 4; i++) { int newX = x + fx[i], newY = y + fy[i]; if(newX >= 0 && newX < n && newY >= 0 && newY < n && !isVisited[newX][newY] && grid[newX][newY] <= mid){ if(dfs(mid, newX, newY, isVisited)) return true; } } return false; } }
-
-
广度优先搜索
-
剑指 Offer II 111. 计算除法
我的题解:
思路:这里我们用哈希表构建图,key代表对应的节点,其值代表与其相连的点,同样时map类型,key2表的时key连接的点,其值val代表权重,即key / key2= val,步骤 构建图 -> 根据queries搜索对应的可行路径,并返回其值,将其值赋值给ans:代表返回的最终结果数组
构建图:扫描equations与values将未加入的点加入map中,并将与其相连的点加入该map的值中,代表与该点相连的点,其值对应values中的值。我们在构建时不但要加入 key / key2= val ,也要加入 key2 / key = 1.0 / val
根据queries对结果进行判断:(这里 a代表 key, b代表 key2)
-
若a 或b 不在map中说明可行的路径不存在,ans(i) = -1.0
-
若字符串 a 和 b 相等则说明自己指向自己, ans(i) = 1.0
-
其它情况,进行广度搜索
初始时,我们建立一个set来代表被访问的点,终止条件就是所有的点都被访问过。 建立一个类Pair,有两个参数,String s, 和 double val,用于存储从根节点到该节点的权值
在队列中加入根节点,pair的值未1.0代表自己指向自己
广度搜索:若队列不为空
每次都获取当前队首的元素,并遍历其所有的边,找到与其相连的点,若该点就是 key2 即我们项找的点, 即说明该路径是存在的 返回即可
若没找到,且与其相连的点未曾访问过,就加入队列中,并标记为已访问,继续。。。
最终无结果就返回 -1.0说明不存在
class Solution { //计算除法-广度优先:这里需要在之前的基础上将中间结果储存起来 //哈希表存储图 Map<String, Map<String, Double>> map = new HashMap<>(); public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) { //创建图 for (int i = 0; i < equations.size(); i++) { String a = equations.get(i).get(0); String b = equations.get(i).get(1); double val = values[i]; //若不存在该节点就创建该节点 if(!map.containsKey(a)){ map.put(a, new HashMap<>()); } if(!map.containsKey(b)){ map.put(b, new HashMap<>()); } //将对应值存入 map.get(a).put(b, val); map.get(b).put(a, 1.0 / val); } //扫描queries int n = queries.size(); double[] ans = new double[n]; for(int i = 0; i < queries.size(); i++){ String a = queries.get(i).get(0); String b = queries.get(i).get(1); //若这两个点有一个不在map中就返回-1.0 if(!map.containsKey(a) || !map.containsKey(b)){ ans[i] = -1.0; //若相等的话,代表是同一个点,值为1.0 }else if(a.equals(b)){ ans[i] = 1.0; //其它情况搜索 }else{ ans[i] = bfs(a, b); } } return ans; } public double bfs(String a, String b){ //标记当前点是否访问 Set<String> visited = new HashSet<>(); //队列 //加入开始节点 Queue<Pair<String, Double>> queue = new LinkedList(){{add(new Pair<String, Double>(a, 1.0));}}; visited.add(a); //搜索向下扩展 while(!queue.isEmpty()){ Pair<String, Double> cur = queue.poll(); String s = cur.getS(); double val = cur.getVal(); //若当前的点为目标 if(s.equals(b)){ //找到了返回 return val; } //若没有找到将当前节点连接的边就加入队列中,其值等于val * 该边的值 for(Map.Entry<String, Double> entry : map.get(s).entrySet()){ String c = entry.getKey(); double v = entry.getValue(); //若不含有就加入 if(!visited.contains(c)){ queue.offer(new Pair<String, Double>(c, v * val)); //标记该节点已访问*** visited.add(c); } } } return -1.0; } class Pair<T, E>{ T s; E val; Pair(T s, E val){ this.s = s; this.val = val; } public T getS() { return s; } public E getVal() { return val; } } }
-
-
547. 省份数量
我的题解:
思路: 广度优先:同样的我们创建长度未n的矩阵用来代表指定的点是否被访问过,最外层是一个for循环代表从该点开始搜索,我们首先加入该点到队列中,然后扫描该点的所有连接的点,若这些点未被访问就加入到队列中,继续向下搜索,该层就相当于是dfs中的第0层,该点未被访问就count+1
public int findCircleNum(int[][] isConnected) { int n = isConnected.length; boolean[] isVisited = new boolean[n]; //计数器 int count = 0; for (int i = 0; i < n; i++) { if(!isVisited[i]){ isVisited[i] = true; Queue<Integer> queue = new LinkedList<>(); //若该点未被访问过就加入到队列中 queue.offer(i); while(!queue.isEmpty()){ int j = queue.poll(); for (int k = 0; k < n; k++) { //若该点未被访问,且联通 if(isConnected[j][k] == 1 && !isVisited[k]){ //加入队列中,标记为已访问 queue.offer(k); isVisited[k] = true; } } } //若未访问即为另外一组,单独计数 count++; } } return count; }
-
由斜杠划分区域
我的题解:
思路: 广度优先遍历,同样的我们将该问题转化为岛屿的个数,这里用广度优先来处理
TIPS:注意将单个单元格长度扩充n倍,宽度扩充n倍,访问时只需要将对应索引长度*n,宽度**n后,后续过程就类似于二维数组的访问过程
//广度优先遍历,同样的我们将该问题转化为岛屿的个数,这里用广度优先来处理 int n; int[][] arr; int count; int[] fx = {0, 0, -1, 1}; int[] fy = {-1, 1, 0, 0}; public int regionsBySlashes(String[] grid) { n = grid.length; arr = new int[3 * n][3 * n]; count = 0; for (int i = 0; i < n; i++) { char[] chars = grid[i].toCharArray(); for (int j = 0; j < n; j++) { if(chars[j] == '/'){ arr[3 * i + 2][3 * j] = arr[3 * i + 1][3 * j + 1] = arr[3 * i][3 * j + 2] = 1; }else if(chars[j] == '\\'){ arr[3 * i][3 * j] = arr[3 * i + 1][3 * j + 1] = arr[3 * i + 2][3 * j + 2] = 1; } } } for (int i = 0; i < 3 * n; i++) { for (int j = 0; j < 3 * n; j++) { if(arr[i][j] == 0){ count++; Queue<int[]> queue = new LinkedList<>(); queue.offer(new int[] {i, j}); arr[i][j] = 1; while(!queue.isEmpty()){ int[] cur = queue.poll(); int x = cur[0]; int y = cur[1]; for (int k = 0; k < 4; k++) { int newX = x + fx[k]; int newY = y + fy[k]; if(newX >= 0 && newX < 3 * n && newY >= 0 && newY < 3 * n && arr[newX][newY] == 0){ arr[newX][newY] = 1; queue.offer(new int[] {newX, newY}); } } } } } } return count; }
-
水位上升的泳池中游泳
我的题解:
思路: 因为泳池中的平台高度是从0~n*n-1是连续的,所以我们可以用二分查找从中间开始枚举可能的高度,从起始位置(0,0)向四周进行扩展,若四周的额高度小于或等于mid,就加入队列中,并判断当前位置是不是
目标位置(n-1, n-1),若是的话标记为true,若不是继续进行搜索
1.初始化n,队列,left和right
2.初始化flag,以当前mid,初始化若当前mid高度比(0, 0)高,我们就创建队列和标记矩阵,并将初始位置加入队列中,并标记该位置为已访问,若队列不为空就从起点(0, 0)向四周进行扩展,若合法就继续,若找到了目标位置(n-1, n-1)就将flag设为true并退出,否则就将当前位置加入队列中并进行标记已访问以便下一轮继续扩展。3.当队列中元素为空时,就进行判断,对范围进行压缩,若flag = true,则说明mid >= 目标,因此 right = mid,否则left = mid + 1
4.返回left
int[] fx = {0, 0, -1, 1}, fy = {-1, 1, 0, 0}; public int swimInWater(int[][] grid) { //step1 int n = grid.length, left = 0, right = n * n - 1; //step2 while(left < right){ int mid = (left + right) >> 1; boolean flag = false; if(mid >= grid[0][0]){ Queue<int[]> queue = new ArrayDeque<>(); queue.offer(new int[]{0, 0}); boolean[][] isVisited = new boolean[n][n]; isVisited[0][0] = true; while(!queue.isEmpty()){ int[] cur = queue.poll(); int x = cur[0], y = cur[1]; for (int i = 0; i < 4; i++) { int newX = x + fx[i], newY = y + fy[i]; if(newX >= 0 && newX < n && newY >= 0 && newY < n && !isVisited[newX][newY] && grid[newX][newY] <= mid){ if(newX == n - 1 && newY == n - 1){ flag = true; break; } isVisited[newX][newY] = true; queue.offer(new int[] {newX, newY}); } } } } //step3 if(flag){ right = mid; }else{ left = mid + 1; } } //step4 return left; }
-
-
Floyd算法
-
剑指 Offer II 111. 计算除法
我的题解:
思路:这里我们将每一个在equations中出现的字符串,记为一个点,并给它一个id编号,并将该字符串与编号的关系记录到哈希表中,最后得到字符串总的个数n,根据这个构建邻接矩阵,并对邻接矩阵进行初始化,初始值为-1.0表示不可联通。主对角线上的元素赋值为1.0表示可以联通。然后遍历equations和values完成对图的初步构建。
然后我们用Floyd算法,将剩余联通的点在邻接矩阵上进行更新。
创建ans数组,并对queries进行搜索,tmp初始值为-1.0默认不联通
-
-
若这两个字符都在map,就根据邻接矩阵中的值对tmp进行更新
- 其它情况则不变
将tmp赋给ans(i)
最后返回ans
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) { //节点总数 int n = 0; //存储对应点的坐标,标记为 0 ~ n-1 Map<String, Integer> map = new HashMap<>(); //遍历equations for (int i = 0; i < equations.size(); i++) { String a = equations.get(i).get(0); String b = equations.get(i).get(1); //若不存在map中就加入 if(!map.containsKey(a)){ map.put(a, n++); } if(!map.containsKey(b)){ map.put(b, n++); } } //邻接矩阵 double[][] arr = new double[n][n]; //初始化为-1.0表示无路径 for (int i = 0; i < n; i++) { Arrays.fill(arr[i], -1.0); //自己到自己是1.0 arr[i][i] = 1.0; } //将values中的值加入其中 for (int i = 0; i < equations.size(); i++) { int n1 = map.get(equations.get(i).get(0)); int n2 = map.get(equations.get(i).get(1)); arr[n1][n2] = values[i]; arr[n2][n1] = 1.0 / values[i]; } for (int k = 0; k < n; k++) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if(arr[i][k] > 0 && arr[j][k] > 0){ arr[i][j] = arr[i][k] * arr[k][j]; } } } } //扫描queries获取目标 double[] ans = new double[queries.size()]; for (int i = 0; i < queries.size(); i++) { String s1 = queries.get(i).get(0); String s2 = queries.get(i).get(1); double tmp = -1.0; //若s1,s2都在map中 if(map.containsKey(s1) && map.containsKey(s2)){ int n1 = map.get(s1); int n2 = map.get(s2); tmp = arr[n1][n2]; } ans[i] = tmp; } return ans; }
-
Dijkstra(迪杰斯特拉算法)
-
最小体力消耗路径
我的题解:
思路:迪杰斯特拉算法,我们使用优先队列对权值进行排序,优先弹出权值小的,使用weight数组存储从头结点到该位置的最小花费,我们对已访问的位置进行标记,然后我们判断当前路径的权值是否比之前路径的权值小,若是我们更新该路径,并将该点的坐标与路径的权值加入队列中,若当前点的位置为尾结点就终止,并返回尾结点,这里因为是排序过后的能确保体力消耗最小的路径就是该路径。
public int minimumEffortPath(int[][] heights) { int[] fx = {0, 0, -1, 1}; int[] fy = {-1, 1, 0, 0}; int m = heights.length; int n = heights[0].length; int[] weight = new int[m * n]; Arrays.fill(weight, Integer.MAX_VALUE); weight[0] = 0; boolean[] isVisited = new boolean[m * n]; PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((o1, o2) -> o1[2] - o2[2]); //开始的花费为0 priorityQueue.add(new int[]{0, 0, 0}); while(!priorityQueue.isEmpty()){ int[] cur = priorityQueue.poll(); int x = cur[0]; int y = cur[1]; int w = cur[2]; int id = n * x + y; //若当前位置已访问就跳过 if(isVisited[id]) continue; //若当前位置为末尾,就直接跳出 if(x == m - 1 && y == n - 1){ break; } //标记当前位置为已访问 isVisited[id] = true; //四个方向扩展 for (int i = 0; i < 4; i++) { int newX = x + fx[i]; int newY = y + fy[i]; //核心:若当前位置合法即,不越界,且未访问,且当前路径花费小于目标路径花费 if(newX >= 0 && newX < m && newY >= 0 && newY < n && !isVisited[newX * n + newY] && Math.max(heights[x][y] - heights[newX][newY], w) < weight[newX * n + newY] ){ //更新目标路径,且加入队列中 weight[newX * n + newY] = Math.max(w, Math.abs(heights[x][y] - heights[newX][newY])); priorityQueue.offer(new int[] {newX, newY, weight[newX * n + newY]}); } } } return weight[m * n - 1]; }
-
水位上升的泳池中游泳
我的题解:
思路:
Dijkstra算法:该题相当于求从(0, 0)到(n-1, n-1)的最小的路径,每一个点与周围的四个点联通,而且权值无负值因此可以考虑用dijkstra算法
1.初始化优先队列,初始化visited,初始化weight,表示从(0, 0)到当前节点的最小的体力消耗
2.当队列不为空时,获取当前位置的坐标值,先看是不是目标位置,若是则直接返回该值,否则就向四个方向扩展看是否合法,若合法就更新weight并加入优先队列中
3.若达不到目标位置返回0int[] fx = {0, 0, -1, 1}, fy = {-1, 1, 0, 0}; public int swimInWater(int[][] grid) { //step1 int n = grid.length; boolean[][] visited = new boolean[n][n]; int[][] weight = new int[n][n]; for (int i = 0; i < n; i++) { Arrays.fill(weight[i], n * n); } PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(Comparator.comparingInt(a -> grid[a[0]][a[1]])); priorityQueue.offer(new int[]{0, 0}); visited[0][0] = true; weight[0][0] = grid[0][0]; //step2 while(!priorityQueue.isEmpty()){ int[] cur = priorityQueue.poll(); int x = cur[0], y = cur[1]; if(x == n - 1 && y == n - 1) return weight[n - 1][n - 1]; for (int i = 0; i < 4; i++) { int newX = x + fx[i], newY = y + fy[i]; //在路径中取一个最大值,作为当前最小消耗,若这个值小于目标值,就更新它 if(newX >= 0 && newX < n && newY >= 0 && newY < n && !visited[newX][newY] && Math.max(grid[newX][newY], weight[x][y]) < weight[newX][newY]){ weight[newX][newY] = Math.max(grid[newX][newY], weight[x][y]); visited[newX][newY] = true; priorityQueue.offer(new int[] {newX, newY}); } } } //step3 return -1; }
-