Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1896. Minimum Cost to Change the Final Value of Expression - Leetcode Solution

Problem Description

Given a boolean expression as a string expression consisting only of the characters '0' (false), '1' (true), '&' (AND), '|' (OR), '(', and ')', you are to determine the minimum number of changes needed to flip the final value of the expression. You may change any '0' to '1' or vice versa, or change any '&' to '|' or vice versa. Each change costs 1.

The expression is always valid and fully parenthesized. Your task is to find the minimum cost required to change the final evaluated result of the expression (i.e., if the expression currently evaluates to 1, find the minimum cost to make it evaluate to 0, and vice versa).

  • Each change can be applied to any character, but you cannot change a character more than once.
  • You must not remove or add any elements—only flip characters as described.
  • The expression is non-empty and contains only the specified characters.

Thought Process

At first glance, one might consider brute-forcing all possible single-character changes and evaluating the result each time, but this quickly becomes infeasible for large expressions. Instead, we must recognize that the problem can be approached recursively: every sub-expression can be evaluated for the minimum cost to make it 0 or 1.

By thinking recursively, we can break down the expression into its components, calculate the minimum cost to achieve both possible outcomes at each node, and then combine these results intelligently. This is similar to evaluating an expression tree, where each node represents an operator and each leaf a value.

The key insight is to propagate the minimum cost for both 0 and 1 up the tree, considering both flipping values and operators at each step. This avoids redundant work and ensures efficiency.

Solution Approach

We solve the problem by parsing the expression and, for every subexpression, computing two values:

  • The minimum cost to make the subexpression evaluate to 0.
  • The minimum cost to make the subexpression evaluate to 1.
This is done recursively. Here's a step-by-step breakdown:
  1. Use a stack to parse the fully parenthesized expression. Each time we encounter a digit ('0' or '1'), push a tuple representing its costs (cost_to_0, cost_to_1).
  2. When an operator is encountered, and a closing parenthesis is found, pop the two most recent value tuples and the operator, then compute the minimum costs for both outcomes for the new subexpression.
  3. For each operator, consider both using it as-is and flipping it. For instance, for '&':
    • To get 1: both sides must be 1 (cost is sum of both cost_to_1).
    • To get 0: at least one side is 0 (cost is min of left cost_to_0 + right cost_to_1, etc.).
    • Additionally, consider flipping the operator (cost +1), and recalculate as if it were '|'.
  4. Continue until the expression is fully parsed, and return the minimum cost to flip the final result.

We use a stack-based approach to avoid explicit recursion, and memoize results for efficiency.

Example Walkthrough

Consider the expression "1&(0|1)":

  • First, parse 0|1:
    • To get 1: either side is 1. So, cost = min(left cost_to_1 + right cost_to_1, etc.).
    • To get 0: both sides must be 0.
  • Then, combine with 1&(...):
    • To get 1: both must be 1.
    • To get 0: at least one is 0.
  • At each step, consider flipping digits or operators for the minimum cost.
  • Suppose the original evaluates to 1, and we want 0. The minimum cost is 1, since flipping the '&' to '|' will suffice.

Code Implementation

class Solution:
    def minOperationsToFlip(self, expression: str) -> int:
        stack = []

        for c in expression:
            if c == ' ':
                continue
            elif c == '(':
                stack.append(c)
            elif c in '01':
                # (cost to make 0, cost to make 1, current value)
                stack.append((int(c) ^ 1, 0 if c == '1' else 1, int(c)))
            elif c in '&|':
                stack.append(c)
            elif c == ')':
                right = stack.pop()
                op = stack.pop()
                left = stack.pop()
                stack.pop()  # remove '('

                # left and right: (cost_to_0, cost_to_1, value)
                l0, l1, lv = left
                r0, r1, rv = right

                # As is
                if op == '&':
                    v = lv & rv
                    cost0 = min(
                        l0 + r0,
                        l0 + r1,
                        l1 + r0
                    )
                    cost1 = l1 + r1
                else:
                    v = lv | rv
                    cost1 = min(
                        l1 + r1,
                        l0 + r1,
                        l1 + r0
                    )
                    cost0 = l0 + r0

                # If flip operator
                if op == '&':
                    # as if it was '|'
                    flip_cost1 = min(
                        l1 + r1,
                        l0 + r1,
                        l1 + r0
                    ) + 1
                    flip_cost0 = l0 + r0 + 1
                else:
                    # as if it was '&'
                    flip_cost0 = min(
                        l0 + r0,
                        l0 + r1,
                        l1 + r0
                    ) + 1
                    flip_cost1 = l1 + r1 + 1

                min0 = min(cost0, flip_cost0)
                min1 = min(cost1, flip_cost1)

                stack.append((min0, min1, v))

        # At the end, only one tuple remains
        cost0, cost1, val = stack[0]
        return cost0 if val == 1 else cost1
      
class Solution {
public:
    int minOperationsToFlip(string expression) {
        stack<any> st;

        for (char c : expression) {
            if (c == ' ') continue;
            if (c == '(') {
                st.push(c);
            } else if (c == '0' || c == '1') {
                int v = c - '0';
                st.push(tuple<int,int,int>(v == 0 ? 0 : 1, v == 1 ? 0 : 1, v));
            } else if (c == '&' || c == '|') {
                st.push(c);
            } else if (c == ')') {
                auto right = any_cast<tuple<int,int,int>>(st.top()); st.pop();
                char op = any_cast<char>(st.top()); st.pop();
                auto left = any_cast<tuple<int,int,int>>(st.top()); st.pop();
                st.pop(); // '('

                int l0, l1, lv, r0, r1, rv;
                tie(l0, l1, lv) = left;
                tie(r0, r1, rv) = right;

                int cost0, cost1, flip_cost0, flip_cost1, v;
                if (op == '&') {
                    v = lv & rv;
                    cost0 = min({l0 + r0, l0 + r1, l1 + r0});
                    cost1 = l1 + r1;
                    flip_cost1 = min({l1 + r1, l0 + r1, l1 + r0}) + 1;
                    flip_cost0 = l0 + r0 + 1;
                } else {
                    v = lv | rv;
                    cost1 = min({l1 + r1, l0 + r1, l1 + r0});
                    cost0 = l0 + r0;
                    flip_cost0 = min({l0 + r0, l0 + r1, l1 + r0}) + 1;
                    flip_cost1 = l1 + r1 + 1;
                }
                int min0 = min(cost0, flip_cost0);
                int min1 = min(cost1, flip_cost1);
                st.push(tuple<int,int,int>(min0, min1, v));
            }
        }
        auto res = any_cast<tuple<int,int,int>>(st.top());
        int cost0, cost1, val;
        tie(cost0, cost1, val) = res;
        return val == 1 ? cost0 : cost1;
    }
};
      
import java.util.*;

class Solution {
    public int minOperationsToFlip(String expression) {
        Deque<Object> stack = new ArrayDeque<>();
        for (char c : expression.toCharArray()) {
            if (c == ' ') continue;
            if (c == '(') {
                stack.push(c);
            } else if (c == '0' || c == '1') {
                int v = c - '0';
                stack.push(new int[]{v == 0 ? 0 : 1, v == 1 ? 0 : 1, v});
            } else if (c == '&' || c == '|') {
                stack.push(c);
            } else if (c == ')') {
                int[] right = (int[]) stack.pop();
                char op = (char) stack.pop();
                int[] left = (int[]) stack.pop();
                stack.pop(); // '('

                int l0 = left[0], l1 = left[1], lv = left[2];
                int r0 = right[0], r1 = right[1], rv = right[2];

                int cost0, cost1, flip_cost0, flip_cost1, v;
                if (op == '&') {
                    v = lv & rv;
                    cost0 = Math.min(Math.min(l0 + r0, l0 + r1), l1 + r0);
                    cost1 = l1 + r1;
                    flip_cost1 = Math.min(Math.min(l1 + r1, l0 + r1), l1 + r0) + 1;
                    flip_cost0 = l0 + r0 + 1;
                } else {
                    v = lv | rv;
                    cost1 = Math.min(Math.min(l1 + r1, l0 + r1), l1 + r0);
                    cost0 = l0 + r0;
                    flip_cost0 = Math.min(Math.min(l0 + r0, l0 + r1), l1 + r0) + 1;
                    flip_cost1 = l1 + r1 + 1;
                }
                int min0 = Math.min(cost0, flip_cost0);
                int min1 = Math.min(cost1, flip_cost1);
                stack.push(new int[]{min0, min1, v});
            }
        }
        int[] res = (int[]) stack.pop();
        int cost0 = res[0], cost1 = res[1], val = res[2];
        return val == 1 ? cost0 : cost1;
    }
}
      
var minOperationsToFlip = function(expression) {
    let stack = [];
    for (let c of expression) {
        if (c === ' ') continue;
        if (c === '(') {
            stack.push(c);
        } else if (c === '0' || c === '1') {
            let v = Number(c);
            stack.push([v === 0 ? 0 : 1, v === 1 ? 0 : 1, v]);
        } else if (c === '&' || c === '|') {
            stack.push(c);
        } else if (c === ')') {
            let right = stack.pop();
            let op = stack.pop();
            let left = stack.pop();
            stack.pop(); // '('

            let [l0, l1, lv] = left;
            let [r0, r1, rv] = right;

            let cost0, cost1, flip_cost0, flip_cost1, v;
            if (op === '&') {
                v = lv & rv;
                cost0 = Math.min(l0 + r0, l0 + r1, l1 + r0);
                cost1 = l1 + r1;
                flip_cost1 = Math.min(l1 + r1, l0 + r1, l1 + r0) + 1;
                flip_cost0 = l0 + r0 + 1;
            } else {
                v = lv | rv;
                cost1 = Math.min(l1 + r1, l0 + r1, l1 + r0);
                cost0 = l0 + r0;
                flip_cost0 = Math.min(l0 + r0, l0 + r1, l1 + r0) + 1;
                flip_cost1 = l1 + r1 + 1;
            }
            let min0 = Math.min(cost0, flip_cost0);
            let min1 = Math.min(cost1, flip_cost1);
            stack.push([min0, min1, v]);
        }
    }
    let [cost0, cost1, val] = stack[0];
    return val === 1 ? cost0 : cost1;
};
      

Time and Space Complexity

Brute-force approach: Trying all possible single-character changes and evaluating the expression each time would result in exponential time complexity, as every character can be flipped, and each flip can affect the overall outcome. This is not feasible for large expressions.

Optimized approach (as above): Each character in the expression is processed a constant number of times, and for each subexpression, we compute and store two values (cost to 0 and cost to 1). Thus:

  • Time Complexity: O(N), where N is the length of the expression, since each character is pushed and popped from the stack at most once.
  • Space Complexity: O(N), due to the stack used for parsing and storing intermediate results.
The approach is efficient and scalable for large input sizes.

Summary

This problem is elegantly solved by treating the expression as a tree and recursively (or iteratively with a stack) computing the minimum cost to flip each subexpression. By considering both flipping values and operators at each step, and propagating these costs upward, we avoid brute-force enumeration and achieve a linear-time solution. The key insight is to always keep track of both possible outcomes for each subexpression, enabling an optimal solution with minimal work.