【中等】力扣算法题解析LeetCode399:除法求值

关注文末推广名片,即可免费获得本题测试源码

题目来源:LeetCode 399. 除法求值

问题抽象: 给定一组除法等式 equations(每个等式为 [a_i, b_i, val_i] 表示 a_i / b_i = val_i)和一组查询 queries(每个查询为 [c_j, d_j]),需计算每个查询 c_j / d_j 的结果,满足以下核心需求:

  1. 计算规则

    • 利用已知等式 推导未知查询(如已知 a/b=2.0b/c=3.0,则 a/c = 2.0×3.0=6.0);
    • 若查询变量无关联或未定义,返回 -1.0
  2. 图论建模

    • 有向带权图:变量为节点,边 a→b 权重 val(表示 a/b=val),边 b→a 权重 1/val
    • 路径计算:查询 c_j/d_j 等价于从 c_jd_j 的路径权重乘积(DFS 或 BFS 遍历)。
  3. 边界处理

    • 变量未定义:若查询中的变量未出现在等式中,返回 -1.0
    • 相同变量查询c_j = d_j 时返回 1.0(即使变量未定义);
    • 无连通路径:若变量间无路径连接,返回 -1.0
    • 零除保护:输入保证等式 val_i > 0(无非正数权重)。
  4. 优化目标

    • 避免对每个查询独立遍历全图(时间复杂度 O(q·n) 不可接受,q 为查询数,n 为变量数);
    • 预处理建图后,使用 并查集Floyd 算法 预计算所有变量对的最短路径权重。
  5. 计算约束

    • 时间复杂度 O(n³ + q)
      • Floyd 算法 O(n³) 预计算所有节点对的最短路径权重;
      • 查询响应 O(1)
    • 空间复杂度 O(n²):存储所有节点对的权重矩阵;
    • 输入规模:
      • 变量数 n ≤ 20(等式数量 ≤ 20);
      • 查询数 q ≤ 20

输入:等式列表 equations(每个元素 [a_i, b_i, val_i]a_i, b_i 为字符串,val_i > 0);查询列表 queries(格式同 equations 但无权重)。
输出:浮点数数组 ansans[j] 为查询 queries[j] 的结果,无解时 -1.0)。


解题思路

该问题可以建模为带权图问题,使用带权并查集高效解决:

  1. 图建模:每个变量为节点,方程式 A/B = v 表示节点 AB 的有向边(权重 v),反向边权重为 1/v
  2. 并查集设计
    • parent[]:记录每个节点的根节点。
    • weight[]:记录节点到根节点的权重乘积(节点值 = 根节点值 × 权重)。
  3. 核心操作
    • 查找:递归寻找根节点,同时进行路径压缩和权重更新(当前权重 = 当前权重 × 父节点权重)。
    • 合并:若 A/B = v,找到根节点 rootArootB。若不同根,将 rootA 的父节点设为 rootB,并更新权重为 (v × weight[B]) / weight[A]
  4. 查询处理:对于查询 C/D
    • CD 未出现,返回 -1.0
    • 若同根,结果为 weight[C] / weight[D];否则返回 -1.0

代码实现(Java版)🔥点击下载源码

class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        // 映射变量名到唯一ID
        Map<String, Integer> varToId = new HashMap<>();
        int id = 0;
        for (List<String> eq : equations) {
            for (String var : eq) {
                if (!varToId.containsKey(var)) {
                    varToId.put(var, id++);
                }
            }
        }
        
        // 初始化并查集
        int n = varToId.size();
        int[] parent = new int[n];
        double[] weight = new double[n]; // 节点到父节点的权重
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            weight[i] = 1.0;
        }
        
        // 合并操作:处理方程式
        for (int i = 0; i < equations.size(); i++) {
            List<String> eq = equations.get(i);
            int aId = varToId.get(eq.get(0));
            int bId = varToId.get(eq.get(1));
            double value = values[i];
            
            int rootA = find(parent, weight, aId);
            int rootB = find(parent, weight, bId);
            
            if (rootA != rootB) {
                parent[rootA] = rootB; // 合并根节点
                weight[rootA] = (value * weight[bId]) / weight[aId]; // 更新权重
            }
        }
        
        // 处理查询
        double[] res = new double[queries.size()];
        for (int i = 0; i < queries.size(); i++) {
            List<String> query = queries.get(i);
            String c = query.get(0);
            String d = query.get(1);
            
            if (!varToId.containsKey(c) || !varToId.containsKey(d)) {
                res[i] = -1.0;
                continue;
            }
            
            int cId = varToId.get(c);
            int dId = varToId.get(d);
            int rootC = find(parent, weight, cId);
            int rootD = find(parent, weight, dId);
            
            if (rootC != rootD) {
                res[i] = -1.0; // 不连通
            } else {
                res[i] = weight[cId] / weight[dId]; // 权重比值
            }
        }
        return res;
    }
    
    // 查找根节点,同时路径压缩和更新权重
    private int find(int[] parent, double[] weight, int x) {
        if (parent[x] != x) {
            int origin = parent[x];
            parent[x] = find(parent, weight, parent[x]); // 递归找根
            weight[x] *= weight[origin]; // 更新权重:当前节点权重 × 父节点权重
        }
        return parent[x];
    }
}

代码说明

  1. 变量映射:使用 HashMap 将字符串变量名映射为整数ID,便于并查集操作。
  2. 并查集初始化:每个节点初始父节点为自身,初始权重为 1.0
  3. 合并操作
    • 对于每个方程式 A/B = v,找到 AB 的根节点。
    • 若根节点不同,将 A 的根节点指向 B 的根节点,并更新权重为 (v × weight[B]) / weight[A](由数学关系推导)。
  4. 查询处理
    • 检查变量是否存在,不存在直接返回 -1.0
    • 若变量同根,结果 = weight[C] / weight[D](因 C = weight[C] × rootD = weight[D] × root)。
  5. 查找优化:在 find() 函数中递归压缩路径,同时更新权重(当前节点权重乘父节点权重),确保后续查询高效。
  6. 复杂度:预处理 O ( N α ( N ) ) O(Nα(N)) O(Nα(N)),查询 O ( Q α ( N ) ) O(Qα(N)) O(Qα(N)) N N N 为变量数, Q Q Q 为查询数, α α α 为反阿克曼函数,接近常数)。

提交详情(执行用时、内存消耗)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

达文汐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值