399. 除法求值【 力扣(LeetCode) 】

零、LeetCode 原题


399. 除法求值

一、题目描述

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

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

返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -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 ]
注意:x 是未定义的 => -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 由小写英文字母与数字组成

三、解题思路

3.1 图的路径搜索

  1. 基本思路:
      如果将每个 equations 看作 边 ,value 看作 边权,则 queries 相当于查询某条路径的权重和。
  2. 具体思路:
    • 构建有向图
    • 路径搜索
      • 如果顶点不存在,则存入 -1 ;
      • 如果顶点相同,则存入 1;
      • 使用深度搜索进行路径搜索,查找该路径并计算权重累加和。

3.2 路径压缩

  1. 基本思路:
      就在上一个方法的基础上,进行路径压缩即可。每搜索完一个,将结果保存。
  2. 具体思路:
      同上,在最后一步搜索完路径时,保存结果,可以作为下次搜索使用。

四、参考代码

4.1 图的路径搜索

时间复杂度: O ( k ∣ E ∣ ) \Omicron(k|E|) O(kE)【查找 k 条路径,每条路径最坏情况就是遍历所有的边】
空间复杂度: O ( ∣ E ∣ ) \Omicron(|E|) O(E)【使用空间有:图的边,图的顶点(最坏2倍边的空间),递归深度(最坏遍历所有边),已搜索顶点集合(最坏搜索过所有顶点)】

class Solution {
public:
    unordered_map<string, unordered_map<string, double>> m;

    double dfs(string now, string obj, unordered_set<string>& used) {
        if (m.count(now) == 0)
            return 0;
        if (m[now].count(obj))
            return m[now][obj];
            
        for (const auto& next : m[now]) {
            if (used.count(next.first))
                continue;
            used.emplace(next.first);

            auto ans = dfs(next.first, obj, used);
            if (ans)
                return ans * next.second;
        }

        return 0;
    }

    vector<double> calcEquation(vector<vector<string>>& equations,
                                vector<double>& values,
                                vector<vector<string>>& queries) {
        vector<double> ans;

        for (int i = 0; i < equations.size(); i++) {
            m[equations[i][0]].emplace(equations[i][1], values[i]);
            m[equations[i][1]].emplace(equations[i][0], 1 / values[i]);
        }

        for (int i = 0; i < queries.size(); i++) {
            if (m.count(queries[i][0]) == 0 || m.count(queries[i][1]) == 0) {
                ans.emplace_back(-1.0);
            } else if (queries[i][0] == queries[i][1]) {
                ans.emplace_back(1.0);
            } else {
                unordered_set<string> used;
                ans.emplace_back(dfs(queries[i][0], queries[i][1], used));
                if (ans.back() == 0.0)
                    ans.back() = -1.0;
            }
        }

        return ans;
    }
};

4.2 路径压缩

时间复杂度: O ( k α ( ∣ E ∣ ) ) \Omicron(k\alpha(|E|)) O(kα(E)) α ( n ) \alpha(n) α(n) 是一个增长很慢的函数,其值都不超过 4】
空间复杂度: O ( ∣ E ∣ ) \Omicron(|E|) O(E)

class Solution {
public:
    unordered_map<string, unordered_map<string, double>> m;
    
    double dfs(string now, string obj, unordered_set<string>& used) {
        if (m.count(now) == 0)
            return 0;
        if (m[now].count(obj))
            return m[now][obj];
        for (const auto& next : m[now]) {
            if (used.count(next.first))
                continue;
            used.emplace(next.first);

            auto ans = dfs(next.first, obj, used);
            if (ans)
                return ans * next.second;
        }

        return 0;
    }
    
    vector<double> calcEquation(vector<vector<string>>& equations,
                                vector<double>& values,
                                vector<vector<string>>& queries) {

        vector<double> ans;

        for (int i = 0; i < equations.size(); i++) {
            m[equations[i][0]].emplace(equations[i][1], values[i]);
            m[equations[i][1]].emplace(equations[i][0], 1 / values[i]);
        }
        for (int i = 0; i < queries.size(); i++) {
            if (m.count(queries[i][0]) == 0 || m.count(queries[i][1]) == 0) {
                ans.emplace_back(-1.0);
            } else if (queries[i][0] == queries[i][1]) {
                ans.emplace_back(1.0);
            } else {
                unordered_set<string> used;
                ans.emplace_back(dfs(queries[i][0], queries[i][1], used));
                if (ans.back() == 0.0)
                    ans.back() = -1.0;
                else{ // 保存结果
                   m[queries[i][0]].emplace(queries[i][1],ans.back()); 
                   m[queries[i][1]].emplace(queries[i][0],1/ans.back()); 
                }
            }
        }

        return ans;
    }
};
### 力扣LeetCode)Java 解法示例 以下是几个经典的 LeetCode 问题及其对应的 Java 解法: #### 1. **无重复字符的最长子串** 此问题是通过滑动窗口算法解决的一个经典案例。具体实现如下所示[^1]: ```java public int lengthOfLongestSubstring(String s) { if (s == null || s.length() == 0) return 0; Map<Character, Integer> map = new HashMap<>(); int max = 0; int left = 0; // 左指针初始化为0 for (int i = 0; i < s.length(); i++) { // 右指针遍历字符串 char c = s.charAt(i); if (map.containsKey(c)) { left = Math.max(left, map.get(c) + 1); // 更新左边界 } map.put(c, i); // 记录当前字符的位置 max = Math.max(max, i - left + 1); // 更新最大长度 } return max; } ``` --- #### 2. **组合问题** 该问题可以通过回溯法来求解,其核心思想是逐步构建满足条件的组合并剪枝不必要的分支[^2]。 ```java List<List<Integer>> result = new ArrayList<>(); LinkedList<Integer> path = new LinkedList<>(); public List<List<Integer>> combine(int n, int k) { backtracking(n, k, 1); return result; } private void backtracking(int n, int k, int startIndex) { if (path.size() == k) { result.add(new ArrayList<>(path)); return; } for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { path.add(i); backtracking(n, k, i + 1); path.removeLast(); } } ``` --- #### 3. **三数之和** 对于这个问题,可以采用双指针的方法优化时间复杂度至 O()[^3]: ```java import java.util.*; public class Solution { public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); // 排序数组以便后续去重操作 List<List<Integer>> res = new ArrayList<>(); for (int i = 0; i < nums.length && nums[i] <= 0; ++i) { if (i == 0 || nums[i - 1] != nums[i]) { // 跳过重复元素 twoSum(nums, i, res); } } return res; } private void twoSum(int[] nums, int i, List<List<Integer>> res) { int lo = i + 1, hi = nums.length - 1; while (lo < hi) { int sum = nums[i] + nums[lo] + nums[hi]; if (sum < 0) { ++lo; } else if (sum > 0) { --hi; } else { res.add(Arrays.asList(nums[i], nums[lo++], nums[hi--])); while (lo < hi && nums[lo] == nums[lo - 1]) ++lo; // 去重 } } } } ``` --- #### 4. **构造二叉树** 给定一棵二叉树的中序遍历 `inorder` 和后序遍历 `postorder`,我们可以利用递归来重建这棵树[^4]: ```java class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } public class BuildTree { private Map<Integer, Integer> indexMap; public TreeNode buildTree(int[] inorder, int[] postorder) { indexMap = new HashMap<>(); for (int i = 0; i < inorder.length; i++) { indexMap.put(inorder[i], i); } return helper(0, inorder.length - 1, 0, postorder.length - 1, inorder, postorder); } private TreeNode helper(int inLeft, int inRight, int postLeft, int postRight, int[] inorder, int[] postorder) { if (inLeft > inRight) return null; int rootVal = postorder[postRight]; // 后序最后一个节点即根节点 TreeNode root = new TreeNode(rootVal); int index = indexMap.get(rootVal); // 找到根节点在中序中的位置 int size = index - inLeft; // 左子树大小 root.left = helper(inLeft, index - 1, postLeft, postLeft + size - 1, inorder, postorder); root.right = helper(index + 1, inRight, postLeft + size, postRight - 1, inorder, postorder); return root; } } ``` --- #### 5. **组合总和 II** 针对允许重复值但不允许重复解的情况,我们可以在回溯过程中加入额外判断逻辑以跳过重复项[^5]: ```java List<List<Integer>> result = new ArrayList<>(); Deque<Integer> path = new ArrayDeque<>(); public List<List<Integer>> combinationSum2(int[] candidates, int target) { Arrays.sort(candidates); // 对候选数组排序便于处理重复情况 backtrack(candidates, target, 0, 0); return result; } private void backtrack(int[] candidates, int target, int start, int total) { if (total == target) { result.add(new ArrayList<>(path)); // 添加符合条件的一组解 return; } for (int i = start; i < candidates.length && total + candidates[i] <= target; i++) { if (i > start && candidates[i] == candidates[i - 1]) continue; // 剪枝:去除重复解 path.addLast(candidates[i]); backtrack(candidates, target, i + 1, total + candidates[i]); // 不可重复使用同一元素 path.removeLast(); } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值