Given two binary expression trees, root1
and root2
, each representing a mathematical expression involving only the '+' operator and single-letter variable operands (like 'a'
, 'b'
, etc.), determine whether the two expressions represented by the trees are equivalent.
Two expressions are considered equivalent if, after applying the commutative and associative properties of addition (i.e., a + b == b + a
and (a + b) + c == a + (b + c)
), they represent the same multiset of variables, regardless of their grouping or order.
Key constraints:
'+'
.The naive approach would be to compare the trees structurally or to try all possible permutations of operands, but this quickly becomes infeasible as the number of variables grows. Since addition is commutative and associative, the order and grouping of operands do not matter. Thus, the core insight is that two expressions are equivalent if and only if they contain the same variables with the same frequencies, regardless of tree shape or traversal order.
Instead of comparing tree structures, we can focus on counting the occurrences of each variable in both trees. If the counts match for all variables, the expressions are equivalent.
To determine if two expression trees are equivalent, we will:
This approach is efficient because:
Consider the following two expression trees:
(a + (b + c))
((c + a) + b)
Traversing Tree 1, we encounter variables: a
, b
, c
. Frequency map: {'a': 1, 'b': 1, 'c': 1}
.
Traversing Tree 2, we encounter variables: c
, a
, b
. Frequency map: {'a': 1, 'b': 1, 'c': 1}
.
Since both maps are identical, the trees are equivalent.
If Tree 2 instead represented ((c + a) + b + b)
, the frequency map would be {'a': 1, 'b': 2, 'c': 1}
, which does not match Tree 1's map, so they would not be equivalent.
Brute-force approach: Attempting all permutations or directly comparing all possible tree re-groupings would have factorial time complexity, O(N!), which is infeasible for large trees.
Optimized approach (using frequency maps):
By leveraging the commutative and associative properties of addition, we reduce the problem to counting variable occurrences, ignoring the structure and grouping of the expression trees. This approach is both efficient and robust, providing a clear test for equivalence by simply comparing frequency maps. The solution is elegant because it transforms a potentially complex tree comparison into a straightforward counting problem.
# Definition for a binary tree node.
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def checkEquivalence(self, root1: 'Node', root2: 'Node') -> bool:
def count_vars(node, freq):
if not node:
return
if node.val == '+':
count_vars(node.left, freq)
count_vars(node.right, freq)
else:
freq[node.val] = freq.get(node.val, 0) + 1
freq1 = {}
freq2 = {}
count_vars(root1, freq1)
count_vars(root2, freq2)
return freq1 == freq2
// Definition for a binary tree node.
struct Node {
char val;
Node *left;
Node *right;
Node(char x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
void countVars(Node* node, unordered_map<char, int>& freq) {
if (!node) return;
if (node->val == '+') {
countVars(node->left, freq);
countVars(node->right, freq);
} else {
freq[node->val]++;
}
}
bool checkEquivalence(Node* root1, Node* root2) {
unordered_map<char, int> freq1, freq2;
countVars(root1, freq1);
countVars(root2, freq2);
return freq1 == freq2;
}
};
// Definition for a binary tree node.
class Node {
char val;
Node left;
Node right;
Node(char val) { this.val = val; }
}
import java.util.*;
class Solution {
private void countVars(Node node, Map<Character, Integer> freq) {
if (node == null) return;
if (node.val == '+') {
countVars(node.left, freq);
countVars(node.right, freq);
} else {
freq.put(node.val, freq.getOrDefault(node.val, 0) + 1);
}
}
public boolean checkEquivalence(Node root1, Node root2) {
Map<Character, Integer> freq1 = new HashMap<>();
Map<Character, Integer> freq2 = new HashMap<>();
countVars(root1, freq1);
countVars(root2, freq2);
return freq1.equals(freq2);
}
}
/**
* Definition for a binary tree node.
* function Node(val, left, right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
*/
var checkEquivalence = function(root1, root2) {
function countVars(node, freq) {
if (!node) return;
if (node.val === '+') {
countVars(node.left, freq);
countVars(node.right, freq);
} else {
freq[node.val] = (freq[node.val] || 0) + 1;
}
}
let freq1 = {};
let freq2 = {};
countVars(root1, freq1);
countVars(root2, freq2);
// Compare frequency maps
let keys1 = Object.keys(freq1);
let keys2 = Object.keys(freq2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (freq1[key] !== freq2[key]) return false;
}
return true;
};