這道題主要涉及的是對樹的理解,相關的算法是BFS、DFS、並查集。
原題
給出方程式 A / B = k, 其中 A 和 B 均爲代表字符串的變量, k 是一個浮點型數字。根據已知方程式求解問題,並返回計算結果。如果結果不存在,則返回 -1.0。
示例 :
給定 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 ]
輸入爲: vector<pair<string, string>> equations, vector<double> values, vector<pair<string, string>> queries(方程式,方程式結果,問題方程式)。 其中 equations.size() == values.size(),即方程式的長度與方程式結果長度相等(程式與結果一一對應),並且結果值均爲正數。以上爲方程式的描述。 返回vector<double>類型。
基于上述例子,輸入如下:
equations(方程式) = [ [“a”, “b”], [“b”, “c”] ], values(方程式結果) = [2.0, 3.0], queries(問題方程式) = [ [“a”, “c”], [“b”, “a”], [“a”, “e”], [“a”, “a”], [“x”, “x”] ].
輸入總是有效的。你可以假設除法運算中不會出現除數爲0的情況,且不存在任何矛盾的結果。
原題url:https://leetcode-cn.com/problems/evaluate-division/
解題
BFS或DFS
一般而言,如果我們知道了a/b和b/c,求a/c的話,可以通過a/b * b/c求得結果。聯想成樹的話,也就是節點與節點之間是否相連。總的來說,我們需要進行關系的轉換。
利用遞歸的話,可以很好寫出代碼,我提供一個 DFS 的例子:
class Solution { public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) { // 記錄出現了哪些字符串 Set<String> keySet = new HashSet<>(); // 記錄等式關系,第一層key爲除數,第二層key爲被除數,第二層value爲結果 Map<String, Map<String, Double>> map = new HashMap<>(); // 當前是第幾個等式,也代表當前要去取第幾個結果 int count = 0; // 遍曆等式 for (List<String> equation : equations) { // 除數 String divisor = equation.get(0); keySet.add(divisor); // 被除數 String divided = equation.get(1); keySet.add(divided); // 結果 double value = values[count]; // 賦值 putValue(value, divisor, divided, map);
count++; } // 計算結果 double[] result = new double[queries.size()]; count = 0; for (List<String> query : queries) { // 除數 String divisor = query.get(0); // 被除數 String divided = query.get(1); // 求結果 result[count] = cal(divisor, divided, map, new HashSet<>(), keySet); count++; }
return result; }
public double cal( String divisor, String divided, Map<String, Map<String, Double>> map, Set<String> divisorSet, Set<String> keySet) { // 但凡除數、被除數有一個不存在,則直接返回-1.0 if (!keySet.contains(divisor) || !keySet.contains(divided)) { return -1.0; } // 根據除數,獲取valueMap Map<String, Double> valueMap = map.get(divisor); // 查找是否有現成結果 Double result = valueMap.get(divided); // 如果有就直接返回 if (result != null) { return result; }
// 沒有就進行計算 for (String key : keySet) { Double value = valueMap.get(key); if (value == null) { continue; } // 如果爲-1.0,說明無法計算 if (value == -1.0) { continue; } // 原本是計算”divisor/divided”,現在可以嘗試求出”divisor/key”和”key/divided” // 如果key的數據已經計算過,則不再重複計算 if (divisorSet.contains(key)) { continue; } divisorSet.add(key); // DFS double tempResult = cal(key, divided, map, divisorSet, keySet); // 記錄中間結果 putValue(tempResult, key, divided, map); // 如果爲-1.0,說明無法計算 if (tempResult == -1.0) { continue; } // 記錄最終結果 putValue(value * tempResult, divisor, divided, map); // 返回結果 return value * tempResult; }
// 說明無法計算 putValue(-1.0, divisor, divided, map); return -1.0; }
public void putValue( double value, String divisor, String divided, Map<String, Map<String, Double>> map) { // 根據除數賦值 Map<String, Double> valueMap = map.get(divisor); if (valueMap == null) { valueMap = new HashMap<>(); map.put(divisor, valueMap); // 記錄”divisor/divisor = 1.0″ valueMap.put(divisor, 1.0); } valueMap.put(divided, value); // 根據被除數賦值 valueMap = map.get(divided); if (valueMap == null) { valueMap = new HashMap<>(); map.put(divided, valueMap); // 記錄”divided/divided = 1.0″ valueMap.put(divided, 1.0); } valueMap.put(divisor, 1.0 / value); } }
提交OK,大家可以嘗試寫一個 BFS(廣度優先搜索) 的版本,需要借用隊列記錄中間遍曆過的節點。
並查集
首先,我們需要了解什麽是並查集,可以參考這一篇博客:https://www.cnblogs.com/noKing/p/8018609.html
我的理解是:當我們知道了一堆元素裏某幾個之間的關聯關系,可以將所有元素歸並到一個集合中,這個集合中所有元素都是有關系的。
雖然並查集在構造時複雜,消耗一定的時間,但它可以提高了查找的效率。
針對這道題目,我們不僅需要記錄 數字 與 數字 之間是否存在關聯,還需要記錄具體的倍數關系。其實你可以簡單理解爲:
當我們知道了: a / b = 3 b / d = 2 c / d = 4 我們可以將 d 看成是根節點,它有子節點 b、c,b有子節點 a
這樣是不是好理解多了。
我是利用一個 HashMap 存儲了節點之間是否關聯,用另一個 HashMap 存儲了節點之間的倍數關系,代碼如下:
class Solution { /** * 並查集 * key : 當前節點 * value : 其父節點(也可以認爲是大哥節點) */ private Map<String, String> parents = new HashMap<>(); /** * 並查集 * key : 當前節點 * value : 父節點(也可以認爲是大哥節點) /當前節點 */ private Map<String, Double> values = new HashMap<>();
private void add(String x) { // 添加節點x if (!parents.containsKey(x)) { parents.put(x, x); values.put(x, 1.0); } }
private void union(String parent, String child, double value) { // 添加節點 add(parent); add(child); // 找到parent和child的最終父節點 String p1 = find(parent); String p2 = find(child); // 如果兩個結果不等 if (!p1.equals(p2)) { // 記錄p1、p2的關系 parents.put(p2, p1); values.put(p2, value * (values.get(parent) / values.get(child))); } }
/** * 找到x的最終父節點 */ private String find(String x) { while (!parents.get(x).equals(x)) { x = parents.get(x); } return x; }
/** * 一直計算到和根節點的關聯 */ private double cal(String x) { double v = values.get(x); while (!parents.get(x).equals(x)) { x = parents.get(x); v *= values.get(x); } return v; }
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) { // 構建並查集 for (int i = 0; i < equations.size(); i++) { union(equations.get(i).get(0), equations.get(i).get(1), values[i]); } // 最終的結果 double[] answer = new double[queries.size()]; // 計算 for (int i = 0; i < queries.size(); i++) { // 需要計算的兩個數 String c1 = queries.get(i).get(0); String c2 = queries.get(i).get(1); // 如果有一個是不存在的,則沒有計算的必要 if (!parents.containsKey(c1) || !parents.containsKey(c2)) { answer[i] = -1; continue; } // 如果兩者相等,則返回1 if (c1.equals(c2)) { answer[i] = 1; continue; } // 找到兩者的最終父節點,也就是各自的根節點 String p1 = find(c1); String p2 = find(c2); // 如果兩者不等,說明兩個節點無法構成關聯 if (!p1.equals(p2)) { answer[i] = -1; continue; } // 計算兩者的結果 answer[i] = cal(c2) / cal(c1); } return answer; } }
提交OK,我的這個寫法中,並查集是沒有進行路徑壓縮的,有興趣的同學可以在此之上進行優化,這樣當 queries 越大時,查找的效率會越高。
總結
以上就是這道題目我的解答過程了,不知道大家是否理解了。這道題主要涉及的是對樹的理解,相關的算法是BFS、DFS、並查集。
有興趣的話可以訪問我的博客或者關注我的公衆號、頭條號,說不定會有意外的驚喜。
https://death00.github.io/
公衆號:健程之道