Boolean Expression Tree with Leaf Flips
Company: Google
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates a candidate's ability to work with tree data structures and apply efficient update propagation techniques. It tests practical understanding of recursive tree evaluation, logical operator semantics, and incremental recomputation by traversing ancestor paths rather than re-evaluating the full tree.
Constraints
- 1 <= number of nodes <= 10^5
- 1 <= len(flips) <= 10^5
- Operators are exactly AND, OR, XOR (n-ary, at least one child) and NOT (exactly one child).
- Leaves are pure boolean constants.
- Each flip path locates an existing leaf; an empty path [] is valid only when the root itself is a leaf.
Examples
Input: (["XOR", ["OR", ["LEAF", false], ["LEAF", true]], ["AND", ["LEAF", true], ["NOT", ["LEAF", true]]]], [[0, 0]])
Expected Output: [true]
Explanation: Initial root = XOR(OR(false,true)=true, AND(true, NOT(true)=false)=false) = true. Flip path [0,0] is the false leaf inside OR -> OR(true,true)=true, AND branch unchanged false, root XOR(true,false)=true.
Input: (["AND", ["LEAF", true], ["LEAF", true]], [[0], [1], [0]])
Expected Output: [false, false, false]
Explanation: Start AND(true,true)=true. Flip child 0 -> AND(false,true)=false. Flip child 1 -> AND(false,false)=false. Flip child 0 back -> AND(true,false)=false.
Input: (["LEAF", true], [[], []])
Expected Output: [false, true]
Explanation: Root is a leaf. Empty path [] toggles the root leaf: true->false, then false->true.
Input: (["OR", ["LEAF", false], ["LEAF", false]], [[0], [1], [0]])
Expected Output: [true, true, true]
Explanation: Start OR(false,false)=false. Flip child 0 -> OR(true,false)=true. Flip child 1 -> OR(true,true)=true. Flip child 0 back -> OR(false,true)=true.
Input: (["NOT", ["LEAF", false]], [[0], [0]])
Expected Output: [false, true]
Explanation: Start NOT(false)=true. Flip the single child false->true -> NOT(true)=false. Flip back true->false -> NOT(false)=true.
Input: (["XOR", ["LEAF", true], ["LEAF", false], ["LEAF", true], ["LEAF", false]], [[1], [3], [0]])
Expected Output: [true, false, true]
Explanation: n-ary XOR = parity of true children. Start [T,F,T,F] -> 2 trues -> false. Flip idx1 F->T: 3 trues -> true. Flip idx3 F->T: 4 trues -> false. Flip idx0 T->F: 3 trues -> true.
Input: (["AND", ["OR", ["LEAF", false], ["LEAF", false]], ["NOT", ["LEAF", false]]], [[0, 1], [0, 1], [1, 0]])
Expected Output: [true, false, false]
Explanation: Start AND(OR(false,false)=false, NOT(false)=true)=false. Flip [0,1] (2nd OR leaf) F->T: OR=true, AND(true,true)=true. Flip [0,1] back T->F: OR=false, AND=false. Flip [1,0] (NOT's leaf) F->T: NOT(true)=false, AND(false,false)=false.
Hints
- Build the tree once and keep, per node, its current boolean value plus a pointer to its parent. Nodes have no parent pointer in the raw encoding, so set parents yourself while building.
- Do a single recursive post-order evaluation to fill every node's initial value before processing any flips.
- A flip can only affect ancestors of the flipped leaf. Toggle the leaf, then recompute each ancestor in turn walking up to the root.
- Early termination: if recomputing an ancestor yields the same value it already had, none of its own ancestors can change either, so stop and record the current root value.
- For AND, short-circuit thinking helps reason about it, but you must recompute from the actual current child values; for XOR the result is the parity (odd count) of true children.