Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

971. Flip Binary Tree To Match Preorder Traversal - Leetcode Solution

Problem Description

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.

  • You are given the root of a binary tree (root) and an array (voyage) representing the desired preorder traversal of the tree.
  • You may flip any node's left and right children (swap them), but you must do so as few times as possible.
  • Your task is to return a list of all nodes where flips occurred, in the order they were flipped. If it's impossible to match the preorder traversal to voyage by flipping, return [-1].
  • Constraints: Each value in the tree and voyage is unique. There is only one valid solution, and you cannot reuse or revisit nodes.

Thought Process

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.

Solution Approach

  • Step 1: Preorder Traversal with Index Tracking
    • Use a variable (e.g. i) to track the current index in voyage as we traverse the tree.
    • At each node, check if the node's value matches voyage[i]. If not, the traversal cannot be matched—return failure.
  • Step 2: Decide When to Flip
    • After verifying the current node, check if the left child exists and if its value matches voyage[i+1].
    • If it does, proceed to traverse left then right (standard preorder).
    • If not, but the right child matches voyage[i+1], flip the children—record the current node's value, traverse right first, then left.
    • If neither child matches, it's impossible to match voyage—return failure.
  • Step 3: Collect Flips
    • Each time a flip is made, record the node's value in a result list.
    • Continue the traversal recursively.
  • Step 4: Return Result
    • If the traversal completes successfully, return the list of flipped nodes.
    • If a mismatch is found, return [-1].

This approach is efficient because we only traverse each node once, and make a flip only when necessary.

Example Walkthrough

Consider the following example:

  • Tree:
            1
           / \
          2   3
          
  • voyage = [1,3,2]
  1. Start at root (1). voyage[0] = 1. Match. i = 0.
  2. Next expected value is voyage[1] = 3.
  3. Left child of 1 is 2 (does not match 3), right child is 3 (matches 3). So, flip needed at node 1.
  4. Record flip at 1. Traverse right (3): voyage[1] = 3. Match. i = 1.
  5. 3 has no children. Backtrack. Traverse left (2): voyage[2] = 2. Match. i = 2.
  6. Traversal complete. Return [1] (flipped at root).

If the voyage was [1,2,3], no flip would be needed; the answer would be [].

Time and Space Complexity

  • Brute-force Approach:
    • Would try all possible flip combinations, leading to exponential time complexity: O(2^N) for N nodes.
  • Optimized Approach (This Solution):
    • Each node is visited once, and each comparison and potential flip is O(1).
    • Therefore, overall time complexity is O(N), where N is the number of nodes.
    • Space complexity is O(N) for the recursion stack (worst case: skewed tree) and the result list.

Summary

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.

Code Implementation

# 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];
};