【Leetcode每日笔记】399. 除法求值(Python)

文章目录

  • 题目
  • 解题思路
    • 构图+DFS
    • 带权并查集
  • 代码

题目

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

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

返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。

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

示例 1:

输入:equations = [[“a”,“b”],[“b”,“c”]], values = [2.0,3.0], queries =
[[“a”,“c”],[“b”,“a”],[“a”,“e”],[“a”,“a”],[“x”,“x”]]
输出:[6.00000,0.50000,-1.00000,1.00000,-1.00000] 解释: 条件:a / b = 2.0, b /
c = 3.0 问题:a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
结果:[6.0, 0.5, -1.0, 1.0, -1.0 ]

示例 2:

输入:equations = [[“a”,“b”],[“b”,“c”],[“bc”,“cd”]], values =
[1.5,2.5,5.0], queries = [[“a”,“c”],[“c”,“b”],[“bc”,“cd”],[“cd”,“bc”]]
输出:[3.75000,0.40000,5.00000,0.20000]

示例 3:

输入:equations = [[“a”,“b”]], values = [0.5], queries =
[[“a”,“b”],[“b”,“a”],[“a”,“c”],[“x”,“y”]]
输出:[0.50000,2.00000,-1.00000,-1.00000]

提示:

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 由小写英文字母与数字组成

解题思路

构图+DFS

先构造图,使用dict实现,其天然的hash可以在in判断时做到O(1)复杂度。
对每个equation如"a/b=v"构造a到b的带权v的有向边和b到a的带权1/v的有向边,
之后对每个query,只需要进行dfs并将路径上的边权重叠乘就是结果了,如果路径不可达则结果为-1。
具体实现见代码

带权并查集

什么是并查集以及带权并查集?
合并过程是将已知条件转换为森林,通过字典实现每个节点关系的连接,并在不断合并的过程中,通过查找不断更新当前合并中有关节点与根节点的权值关系;
查找过程也是更新过程,更新有关节点与根节点的权值。
如a/b = 4,那么在合并中节点a的权值为4,节点b的权值为1。
代码比较长,可以以简单的例子debug一下,跟着走,过程虽然比较绕,但原理并不复杂
例如a/b=2,b/c=2,a/d=2,求c/d=?

其实可以发现,构图+DFS和带权并查集思路是一样的,只是构图+DFS没有合并压缩的操作使每个节点都与根节点挂钩,但使用某个节点时,直接找其根节点即可,而是采用了遍历搜索的形式,而合并和压缩的操作缩短了查询时间,优化整体结构,这就是带权并查集的优势。

代码

    def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
        # 构造图,equations的第一项除以第二项等于value里的对应值,第二项除以第一项等于其倒数
        graph = {
     }
        for (x, y), v in zip(equations, values):
            if x in graph:
                graph[x][y] = v
            else:
                graph[x] = {
     y: v}
            if y in graph:
                graph[y][x] = 1/v
            else:
                graph[y] = {
     x: 1/v}
        
        # dfs找寻从s到t的路径并返回结果叠乘后的边权重即结果
        def dfs(s, t) -> int:
            if s not in graph:
                return -1
            if t == s:
                return 1
            for node in graph[s].keys():
                if node == t:
                    return graph[s][node]
                elif node not in visited:
                    visited.add(node)  # 添加到已访问避免重复遍历
                    v = dfs(node, t)
                    if v != -1:
                        return graph[s][node]*v
            return -1

        # 逐个计算query的值
        res = []
        for qs, qt in queries:
            visited = set()
            res.append(dfs(qs, qt))
        return res
from collections import namedtuple, defaultdict

class Solution:
    def calcEquation(self, equations, values, queries) :
        class Item:
            def __init__(self, parent, value):
                self.parent = parent
                self.value = value

        father = defaultdict(Item)

        def find(x):
        #返回值为根节点
        #如果它是独立元素,根节点是自身,返回自己。不然,递归找到根节点,用路径压缩修改parent,并返回根节点。
            if x != father[x].parent:
                t = father[x].parent
                father[x].parent = find(t)
                father[x].value *= father[t].value  #1---3的权重就是(1--2的权重)*(2---3的权重)
                return father[x].parent
            return x 

        def union(e1, e2, result):
            # 平行四边形法则边 (e1, e2), (e1, f1), (e2, f2) --> (f1, f2)
            #找到根节点判断是否相等
            f1 = find(e1)
            f2 = find(e2)
            #分别属于两个集合,需要合并:
            if f1 != f2:
                father[f1].parent = f2  #第一个根节点指向第二个根节点
                #更新权重,具体方法通过上面的例子可以算一下
                father[f1].value = father[e2].value * result / father[e1].value

        #用字典提高效率,判断是否见过
        number = defaultdict(int)

        #初始化:每个节点的父节点初始化为自身,不指向任何人,value为1
        for i,item in enumerate(equations) :
            s1, s2 = item[0], item[1]
            if s1 not in number:
                number[s1] =  1
                father[s1] = Item(parent=s1, value=1)
            if s2 not in number:
                number[s2] =  1
                father[s2] = Item(parent=s2, value=1)
            #根据两个节点的关系,合并两个节点。构建tree
            union(s1, s2, values[i])

        #开始计算:
        ans = []
        for s1, s2 in queries:
            #从来没见过,直接-1
            if s1 not in number or s2 not in number:
                ans.append(-1.0)
                continue
            #找到根节点,如果不想等,说明在不同集合tree中,没有任何关系,直接-1
            e1, e2 = s1, s2
            f1, f2 = find(e1), find(e2)
            if f1 != f2:
                ans.append(-1.0)
            else:
                #在同一个tree中
                v1 = father[e1].value
                v2 = father[e2].value
                #v1=e1/root   v2=e2/root  则e1/e2=v1/v2
                ans.append(v1 / v2)
        return ans

你可能感兴趣的:(LeetCode一周一结,#,广度优先搜索,数据结构,python,算法,leetcode,并查集)