The Leetcode problem "Flip Binary Tree To Match Preorder Traversal" asks you to determine how to flip a given binary tree so that its preorder traversal matches a specified sequence.
root
) and an array (voyage
) representing the desired preorder traversal of the tree.voyage
by flipping, return [-1]
.voyage
is unique. There is only one valid solution, and you cannot reuse or revisit nodes.
The naive approach might be to try all possible combinations of flips, but this quickly becomes infeasible due to the exponential number of possibilities. Instead, we realize that preorder traversal always visits the root, then the left child, then the right child. If the next value in voyage
doesn't match the current node's left child, we may need to flip.
So, while traversing, we can compare the expected value from voyage
with the actual left child. If it doesn't match, a flip is required at the current node. This insight allows us to optimize the search and avoid unnecessary work.
The main challenge is to keep the traversal in sync with voyage
, flipping only when necessary, and aborting early if a mismatch is found that cannot be fixed by a flip.
i
) to track the current index in voyage
as we traverse the tree.voyage[i]
. If not, the traversal cannot be matched—return failure.voyage[i+1]
.voyage[i+1]
, flip the children—record the current node's value, traverse right first, then left.voyage
—return failure.[-1]
.This approach is efficient because we only traverse each node once, and make a flip only when necessary.
Consider the following example:
1 / \ 2 3
voyage = [1,3,2]
voyage[0] = 1
. Match. i = 0
.voyage[1] = 3
.voyage[1] = 3
. Match. i = 1
.voyage[2] = 2
. Match. i = 2
.[1]
(flipped at root).
If the voyage
was [1,2,3]
, no flip would be needed; the answer would be []
.
The key to solving "Flip Binary Tree To Match Preorder Traversal" efficiently is to traverse the tree in preorder while comparing each node to the expected voyage
value. Flips are made only when necessary, and the process is guided by the next expected value in the voyage
array. This approach ensures an optimal solution with linear time complexity, making it both elegant and efficient.
# Definition for a binary tree node.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def flipMatchVoyage(self, root, voyage):
self.i = 0
self.res = []
def dfs(node):
if not node:
return True
if node.val != voyage[self.i]:
return False
self.i += 1
if node.left and self.i < len(voyage) and node.left.val != voyage[self.i]:
# Flip needed
self.res.append(node.val)
# Traverse right first, then left
return dfs(node.right) and dfs(node.left)
else:
return dfs(node.left) and dfs(node.right)
return self.res if dfs(root) else [-1]
// Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
int i = 0;
vector<int> res;
bool dfs(TreeNode* node, vector<int>& voyage) {
if (!node) return true;
if (node->val != voyage[i]) return false;
i++;
if (node->left && i < voyage.size() && node->left->val != voyage[i]) {
res.push_back(node->val);
return dfs(node->right, voyage) && dfs(node->left, voyage);
} else {
return dfs(node->left, voyage) && dfs(node->right, voyage);
}
}
vector<int> flipMatchVoyage(TreeNode* root, vector<int>& voyage) {
return dfs(root, voyage) ? res : vector<int>{-1};
}
};
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
int i = 0;
List<Integer> res = new ArrayList<>();
public List<Integer> flipMatchVoyage(TreeNode root, int[] voyage) {
return dfs(root, voyage) ? res : Arrays.asList(-1);
}
private boolean dfs(TreeNode node, int[] voyage) {
if (node == null) return true;
if (node.val != voyage[i]) return false;
i++;
if (node.left != null && i < voyage.length && node.left.val != voyage[i]) {
res.add(node.val);
return dfs(node.right, voyage) && dfs(node.left, voyage);
} else {
return dfs(node.left, voyage) && dfs(node.right, voyage);
}
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number[]} voyage
* @return {number[]}
*/
var flipMatchVoyage = function(root, voyage) {
let i = 0;
let res = [];
function dfs(node) {
if (!node) return true;
if (node.val !== voyage[i]) return false;
i++;
if (node.left && i < voyage.length && node.left.val !== voyage[i]) {
res.push(node.val);
return dfs(node.right) && dfs(node.left);
} else {
return dfs(node.left) && dfs(node.right);
}
}
return dfs(root) ? res : [-1];
};