2021-01-06 399. 除法求值[图+BFS,Floyd] (带权并查集)

这篇博客探讨了LeetCode中一道中等难度的题目——399.除法求值,主要涉及图论的解决方法。作者通过三种策略——BFS(广度优先搜索)、邻接矩阵和Floyd算法,详细解析了如何建立图并求解最短路径。在BFS中,采用了邻接表优化存储空间;在邻接矩阵中,利用Floyd算法预计算所有节点间最短距离。文章还提供了完整的代码示例,并讨论了不同方法的适用场景。
摘要由CSDN通过智能技术生成

399.除法求值

[吐槽] 这道标记为中等的题目感觉还挺难的。

思路一:🌟图+BFS

在这里插入图片描述
那么问题来了,我们应该如何表示这张图?

  • Sol1: 邻接矩阵
    虽然矩阵中确实可以存储我们各个边的权重,但是这里有一个问题,我们的节点的名字为2位数字+字母的组合即一共 ( 26 + 10 ) 2 = 1296 (26+10)^2=1296 (26+10)2=1296,比较占用空间
  • Sol2: 🌟 LinkedList(邻接表)
    我们可以用LinkedList表示和a相连的节点。为了存储距离,我们采用LinkedList数组的形式。
    首先,我们对于每一个节点进行从0开始的编号,这是为了用数组。
    List<Pair>[] edges
    这样,我们就可以用edges[i].add(new Pair(b,distance));的方式添加从编号为i的节点出发到编号为b的节点距离为distance的这条边。

官方解答批注

class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        int nvars = 0; //统计equations中一共出现了多少个变量
        Map<String, Integer> variables = new HashMap<String, Integer>();
		
		// 将Equation里面出现的变量名称映射到数字 O(2*n),n 为eq的size
        int n = equations.size();
        for (int i = 0; i < n; i++) {
        	// 起点:equations.get(i).get(0)
            if (!variables.containsKey(equations.get(i).get(0))) {
                variables.put(equations.get(i).get(0), nvars++);
            }
            // 终点:equations.get(i).get(1))
            if (!variables.containsKey(equations.get(i).get(1))) {
                variables.put(equations.get(i).get(1), nvars++);
            }
        }
		
		// 鉴于我们已经完成了映射,我们利用LinkedList的形式给出距离
        // 对于每个点,存储其直接连接到的所有点及对应的权值
        List<Pair>[] edges = new List[nvars];
        // 初始化
        for (int i = 0; i < nvars; i++) {
            edges[i] = new ArrayList<Pair>();
        }
        // 图的构建
        for (int i = 0; i < n; i++) {
            int va = variables.get(equations.get(i).get(0)); //起点的编号,O(1),因为HashMap
            int vb = variables.get(equations.get(i).get(1)); //终点的编号
            edges[va].add(new Pair(vb, values[i]));
            edges[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; // 默认没有回答,即变量名字中没有queries的变量名
            // 如果有回答
            if (variables.containsKey(query.get(0)) && variables.containsKey(query.get(1))) {
                int ia = variables.get(query.get(0)), ib = variables.get(query.get(1)); //获取其编号
                if (ia == ib) {
                    result = 1.0;
                } 
                else {
                	//如果起始点和终点不一样: BFS
                    Queue<Integer> points = new LinkedList<Integer>();
                    points.offer(ia);
                    double[] ratios = new double[nvars];
                    Arrays.fill(ratios, -1.0); // -1 (<0)用来作为我们unvisited的标签。
                    ratios[ia] = 1.0;
					// 如果还有queue里面还有点,且终点还没有走到
                    while (!points.isEmpty() && ratios[ib] < 0) {
                        int x = points.poll();
                        // 检查x周围的每一条边
                        for (Pair pair : edges[x]) {
                            int y = pair.index;
                            double val = pair.value; //x到y的距离
                            if (ratios[y] < 0) {
                                ratios[y] = ratios[x] * val;
                                points.offer(y);  //我们添加我们尚未检测邻居的点到queue中
                            }
                        }
                    }
                    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;
    }
}

收获

  1. 当变量名称变化数量不多的情况可以给每一个点都赋予一个编号Map<String, Integer> variables = new HashMap<String, Integer>();
  2. 邻接表 List<Pair>[] edges = new List[节点数量],edges[i] = new ArrayList<Pair>();
    这样,我们就可以用edges[i].add(new Pair(b,distance));的方式添加从编号为i的节点出发到编号为b的节点距离为distance的这条边。
BFS模版

这里以用Integer编号的点为例子

//queue,先进先出(从而保证了BFS),用来记录尚未检测其邻居的点的编号
Queue<Integer> points = new LinkedList<Integer>(); 
points.offer(startpoint); //添加起始点
boolean[] visited = new boolean[num]; //默认所有的点都是没有visited
// 如果queue中还有点,且终点还没被走到
while(!points.isEmpty()&&visited[endpoint]!=true){
	int x = points.poll(); //未检测其邻居的点的编号
	for(PointType neighbor: edges[x]){
		if(visited[neighbor.indedx]!=true){
			visited[neighbor.indedx]=true;
			points.offer(neighbor.indedx);
		}
	}
}
// 此时如果 visited[endpoint]依旧为False,则说明startpoint和endpoint之间没有通路。

自己重新写的版本:(邻接矩阵)

class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        //第一步:给每一个变量赋予一个index,实现从变量名->index
        int varNum = 0; //也是节点数量
        Map<String,Integer> varMap = new HashMap<>();
        for(int i=0;i<equations.size();i++){
            if(!varMap.containsKey(equations.get(i).get(0)))
                varMap.put(equations.get(i).get(0),varNum++);
            if(!varMap.containsKey(equations.get(i).get(1)))
                varMap.put(equations.get(i).get(1),varNum++);
        }

        //第二步:构建图
        // values.length == equations.length
        List<E>[] graph =new List[varNum];
        // 2.1 为每一个节点初始化列表
        for(int i=0;i<varNum;i++)
            graph[i]=new ArrayList<E>();
        // 2.2 填充每一条边
        for(int i=0;i<equations.size();i++){
            int startIndex=varMap.get(equations.get(i).get(0));
            int endIndex=varMap.get(equations.get(i).get(1));
            graph[startIndex].add(new E(endIndex,values[i])); //因为假设不会存在任何矛盾的结果。
            graph[endIndex].add(new E(startIndex,1.0 /values[i]));
        }

        //第三步:开始回答问题
        int queriesNum = queries.size();
        double[] ans = new double[queriesNum];
        // 一题一题慢慢回答
        for(int i=0;i<queriesNum;i++){
            List<String> question = queries.get(i);
            double res = -1.0; //假设没有答案
            if(varMap.containsKey(question.get(0)) && varMap.containsKey(question.get(1))){
                int start=varMap.get(question.get(0));
                int end=varMap.get(question.get(1));
                if(start==end)
                    res=1.0;
                else{
                    // bfs
                    Queue<Integer> points = new LinkedList<Integer>();
                    points.add(start);
                    double[] visited = new double[varNum];
                    Arrays.fill(visited,-1.0);
                    visited[start]=1.0;
                    while(!points.isEmpty() && visited[end]<0){
                        int pointIndex = points.poll();
                        for(E neighbor:graph[pointIndex]){
                            int neighborIndex = neighbor.index;
                            double neighborDist = neighbor.dist;

                            if(visited[neighborIndex]<0){
                                visited[neighborIndex] = visited[pointIndex]*neighborDist;
                                points.offer(neighborIndex);
                            }
                        }
                    }
                    res = visited[end];
                }
            }
            ans[i] = res;
        }
        return ans;

    }
}

class E{
    int index; //编号
    double dist;  //距离,这里是ratio
    public E(int index,double dist){
        this.index=index;
        this.dist=dist;
    }
}

思路二:🌟Matrix+Floyd

如果查询次数比较多,我们可以利用Floyd算法预先计算出每两个点之间的最短距离。
这里与思路一比较大的区别是,我们不再使用邻接表而使用邻接矩阵来表示这个图。其余如用编号表示字符串的思路是相同的。

class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        //第一步:给每一个变量赋予一个index,实现从变量名->index
        int varNum = 0; //也是节点数量
        Map<String,Integer> varMap = new HashMap<>();
        for(int i=0;i<equations.size();i++){
            if(!varMap.containsKey(equations.get(i).get(0)))
                varMap.put(equations.get(i).get(0),varNum++);
            if(!varMap.containsKey(equations.get(i).get(1)))
                varMap.put(equations.get(i).get(1),varNum++);
        }

        // 第二步:构建图
        // 这里由于题目的特征,我们用-1表示i到j没有路径,我们用邻接矩阵表示图
        double[][] graph = new double[varNum][varNum];
        for(int i=0;i<varNum;i++)
            Arrays.fill(graph[i],-1.0);
        for(int i=0;i<equations.size();i++){
            int startIndex = varMap.get(equations.get(i).get(0));
            int endIndex = varMap.get(equations.get(i).get(1));
            graph[startIndex][endIndex] = values[i];
            graph[endIndex][startIndex] = 1.0/values[i];
        }
        
        // 第三步:直接对Graph进行Floyd
        // 实际上这道题中只要两者之间有通路(>0)那就可以了,因为题目假设告诉我们不存在任何矛盾的结果
        for(int k=0;k<varNum;k++)
            for(int i=0;i<varNum;i++)
                for(int j=0;j<varNum;j++)
                    if(graph[i][k]>0 && graph[k][j]>0)
                        graph[i][j]=graph[i][k]*graph[k][j];

        //第四步:开始回答问题
        int queriesNum = queries.size();
        double[] ans = new double[queriesNum];
        for(int i=0;i<queriesNum;i++){
            List<String> question = queries.get(i);
            double res = -1.0;
            if(varMap.containsKey(question.get(0)) && varMap.containsKey((question.get(1)))){
                int start = varMap.get(question.get(0));
                int end = varMap.get(question.get(1));
                if(graph[start][end]>0)
                    res = graph[start][end];
            }
            ans[i]=res;
        }

        
        return ans;

    }
}

思路三:带权并查集

【待做】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值