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;
}
}
思路: 深度优先搜索,我们对矩阵的每一行进行遍历,每一行代表一个点,从该点开始进行深度搜索,找联通的分量若该联通分量未访问,就继续向下搜索,同时给该点标记为已访问,我们只需要看该行的点有没有被访问就知道该点是不是与其他点联通,若未访问则计数器+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.返回结果left
class 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;
}
}
若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;
}
}
}
思路: 广度优先:同样的我们创建长度未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;
}
思路:这里我们将每一个在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;
}
思路:迪杰斯特拉算法,我们使用优先队列对权值进行排序,优先弹出权值小的,使用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.若达不到目标位置返回0
int[] 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;
}