Bird
Raised Fist0
Interview PrepbacktrackinghardFacebookAmazonGoogle

Expression Add Operators

Choose your preparation mode4 modes available
🎯
Expression Add Operators
hardBACKTRACKINGFacebookAmazonGoogle

Imagine you are building a calculator app that can insert operators between digits to reach a target value. How do you find all valid expressions that evaluate exactly to that target?

💡 This problem is about exploring all possible ways to insert operators between digits to form expressions that evaluate to a target. Beginners struggle because the search space is huge and handling operator precedence, especially multiplication, requires careful tracking of intermediate results.
📋
Problem Statement

Given a string num that contains only digits and an integer target, return all possibilities to add binary operators ('+', '-', or '*') between the digits so that the resultant expression evaluates to the target value. The returned expressions should not contain any spaces and must not have leading zeros in any operand except for the number zero itself.

1 ≤ num.length ≤ 10num consists of only digits.-2^31 ≤ target ≤ 2^31 - 1
💡
Example
Input"num = \"123\", target = 6"
Output["1+2+3", "1*2*3"]

Both expressions evaluate to 6: 1+2+3=6 and 1*2*3=6.

Input"num = \"232\", target = 8"
Output["2*3+2", "2+3*2"]

Expressions 2*3+2=8 and 2+3*2=8 both match the target.

  • num = "000", target = 0 → ["0+0+0", "0+0-0", "0-0+0", "0-0-0", "0*0*0", ...]
  • num = "105", target = 5 → ["1*0+5", "10-5"] (leading zero in '05' is invalid)
  • num = "3456237490", target = 9191 → [] (no valid expressions)
  • num = "1", target = 1 → ["1"] (single digit equals target)
⚠️
Common Mistakes
Not handling leading zeros properly

Expressions like '05' are invalid but included, causing wrong answers

✅ Add a check to skip operands with leading zeros except single '0'

Incorrect multiplication handling

Multiplication evaluated incorrectly, e.g., '2+3*2' evaluated as (2+3)*2=10 instead of 2+(3*2)=8

✅ Track last operand and adjust evaluated value to respect operator precedence

Integer overflow when parsing operands

Runtime errors or incorrect results for large operands

✅ Use 64-bit integers (long/long long) or Python int which is unbounded

Not backtracking string builder length properly

Expressions get concatenated incorrectly, causing wrong outputs or runtime errors

✅ Reset string builder length after each recursive call to backtrack

Not testing edge cases like single digit or all zeros

Solution fails on minimal or tricky inputs

✅ Include edge cases in testing and handle them explicitly

🧠
Brute Force (Pure Recursion with Expression Building)
💡 This approach tries every possible way to insert operators between digits and evaluates the expression on the fly. It is the foundation to understand the problem's search space and operator handling.

Intuition

We recursively pick every possible operand substring and try all operators before it, tracking the current evaluated value and the last operand to handle multiplication correctly.

Algorithm

  1. Start from index 0 and recursively try all possible splits of the remaining string as the next operand.
  2. For each operand, if it's the first one, start the expression without an operator.
  3. Otherwise, try adding '+', '-', and '*' operators before the operand and update the evaluated value accordingly.
  4. Use a helper parameter to track the last operand value to correctly handle multiplication precedence.
  5. If we reach the end of the string and the evaluated value equals the target, add the expression to results.
💡 The hardest part is tracking the evaluated value and last operand to correctly apply multiplication without evaluating the entire expression each time.
</>
Code
from typing import List

def addOperators(num: str, target: int) -> List[str]:
    res = []
    def backtrack(index: int, path: str, evaluated: int, last_operand: int):
        if index == len(num):
            if evaluated == target:
                res.append(path)
            return
        for i in range(index, len(num)):
            if i != index and num[index] == '0':
                break  # skip leading zero number
            curr_str = num[index:i+1]
            curr = int(curr_str)
            if index == 0:
                backtrack(i+1, curr_str, curr, curr)
            else:
                backtrack(i+1, path + '+' + curr_str, evaluated + curr, curr)
                backtrack(i+1, path + '-' + curr_str, evaluated - curr, -curr)
                backtrack(i+1, path + '*' + curr_str, evaluated - last_operand + last_operand * curr, last_operand * curr)
    backtrack(0, '', 0, 0)
    return res

# Driver code
if __name__ == '__main__':
    print(addOperators("123", 6))
    print(addOperators("00", 0))
Line Notes
def addOperators(num: str, target: int) -> List[str]:Defines main function with input types for clarity
def backtrack(index: int, path: str, evaluated: int, last_operand: int):Helper recursive function to build expressions and track evaluation
if index == len(num):Base case: if we've used all digits, check if expression evaluates to target
if i != index and num[index] == '0':Skip operands with leading zeros except single '0'
if index == 0:For first operand, start expression without operator
backtrack(i+1, path + '+' + curr_str, evaluated + curr, curr)Try adding '+' operator and update evaluated and last operand
backtrack(i+1, path + '-' + curr_str, evaluated - curr, -curr)Try adding '-' operator similarly
backtrack(i+1, path + '*' + curr_str, evaluated - last_operand + last_operand * curr, last_operand * curr)Handle multiplication by adjusting evaluated value to respect precedence
import java.util.*;

public class ExpressionAddOperators {
    public List<String> addOperators(String num, int target) {
        List<String> res = new ArrayList<>();
        backtrack(res, new StringBuilder(), num, target, 0, 0, 0);
        return res;
    }

    private void backtrack(List<String> res, StringBuilder path, String num, int target, int index, long evaluated, long lastOperand) {
        if (index == num.length()) {
            if (evaluated == target) {
                res.add(path.toString());
            }
            return;
        }
        int len = path.length();
        for (int i = index; i < num.length(); i++) {
            if (i != index && num.charAt(index) == '0') break; // skip leading zero
            String currStr = num.substring(index, i + 1);
            long curr = Long.parseLong(currStr);
            if (index == 0) {
                path.append(currStr);
                backtrack(res, path, num, target, i + 1, curr, curr);
                path.setLength(len);
            } else {
                path.append('+').append(currStr);
                backtrack(res, path, num, target, i + 1, evaluated + curr, curr);
                path.setLength(len);

                path.append('-').append(currStr);
                backtrack(res, path, num, target, i + 1, evaluated - curr, -curr);
                path.setLength(len);

                path.append('*').append(currStr);
                backtrack(res, path, num, target, i + 1, evaluated - lastOperand + lastOperand * curr, lastOperand * curr);
                path.setLength(len);
            }
        }
    }

    // Driver code
    public static void main(String[] args) {
        ExpressionAddOperators solver = new ExpressionAddOperators();
        System.out.println(solver.addOperators("123", 6));
        System.out.println(solver.addOperators("00", 0));
    }
}
Line Notes
public List<String> addOperators(String num, int target) {Main function signature returning list of expressions
backtrack(res, new StringBuilder(), num, target, 0, 0, 0);Start recursive backtracking with initial parameters
if (i != index && num.charAt(index) == '0') break;Skip operands with leading zeros
if (index == 0) {First operand added without operator
path.append('+').append(currStr);Try '+' operator and recurse
path.append('-').append(currStr);Try '-' operator and recurse
path.append('*').append(currStr);Try '*' operator and handle precedence
path.setLength(len);Backtrack by resetting path length after recursion
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Solution {
public:
    vector<string> addOperators(string num, int target) {
        vector<string> res;
        string path;
        backtrack(num, target, 0, 0, 0, path, res);
        return res;
    }

private:
    void backtrack(const string& num, int target, int index, long long evaluated, long long lastOperand, string& path, vector<string>& res) {
        if (index == num.size()) {
            if (evaluated == target) {
                res.push_back(path);
            }
            return;
        }
        int len = path.size();
        for (int i = index; i < num.size(); ++i) {
            if (i != index && num[index] == '0') break; // skip leading zero
            string currStr = num.substr(index, i - index + 1);
            long long curr = stoll(currStr);
            if (index == 0) {
                path += currStr;
                backtrack(num, target, i + 1, curr, curr, path, res);
                path.resize(len);
            } else {
                path += '+' + currStr;
                backtrack(num, target, i + 1, evaluated + curr, curr, path, res);
                path.resize(len);

                path += '-' + currStr;
                backtrack(num, target, i + 1, evaluated - curr, -curr, path, res);
                path.resize(len);

                path += '*' + currStr;
                backtrack(num, target, i + 1, evaluated - lastOperand + lastOperand * curr, lastOperand * curr, path, res);
                path.resize(len);
            }
        }
    }
};

// Driver code
int main() {
    Solution sol;
    vector<string> res = sol.addOperators("123", 6);
    for (auto& expr : res) cout << expr << endl;
    cout << "---" << endl;
    res = sol.addOperators("00", 0);
    for (auto& expr : res) cout << expr << endl;
    return 0;
}
Line Notes
vector<string> addOperators(string num, int target) {Main function returning vector of valid expressions
backtrack(num, target, 0, 0, 0, path, res);Start recursive backtracking with initial parameters
if (i != index && num[index] == '0') break;Skip operands with leading zeros
if (index == 0) {First operand added without operator
path += '+' + currStr;Try '+' operator and recurse
path += '-' + currStr;Try '-' operator and recurse
path += '*' + currStr;Try '*' operator and handle precedence
path.resize(len);Backtrack by resetting path length after recursion
var addOperators = function(num, target) {
    const res = [];
    function backtrack(index, path, evaluated, lastOperand) {
        if (index === num.length) {
            if (evaluated === target) res.push(path);
            return;
        }
        for (let i = index; i < num.length; i++) {
            if (i !== index && num[index] === '0') break; // skip leading zero
            const currStr = num.substring(index, i + 1);
            const curr = Number(currStr);
            if (index === 0) {
                backtrack(i + 1, currStr, curr, curr);
            } else {
                backtrack(i + 1, path + '+' + currStr, evaluated + curr, curr);
                backtrack(i + 1, path + '-' + currStr, evaluated - curr, -curr);
                backtrack(i + 1, path + '*' + currStr, evaluated - lastOperand + lastOperand * curr, lastOperand * curr);
            }
        }
    }
    backtrack(0, '', 0, 0);
    return res;
};

// Driver code
console.log(addOperators("123", 6));
console.log(addOperators("00", 0));
Line Notes
var addOperators = function(num, target) {Defines main function with parameters
function backtrack(index, path, evaluated, lastOperand) {Recursive helper to build expressions and track evaluation
if (i !== index && num[index] === '0') break;Skip operands with leading zeros
if (index === 0) {First operand added without operator
backtrack(i + 1, path + '+' + currStr, evaluated + curr, curr);Try '+' operator and recurse
backtrack(i + 1, path + '-' + currStr, evaluated - curr, -curr);Try '-' operator and recurse
backtrack(i + 1, path + '*' + currStr, evaluated - lastOperand + lastOperand * curr, lastOperand * curr);Handle multiplication precedence
⏱
Complexity
TimeO(4^n) due to 3 operators and substring splits at each position
SpaceO(n) recursion stack and O(n) for building expressions

At each digit, we can choose to split or not and try 3 operators, leading to exponential growth in possibilities.

💡 For n=10, this means up to 1 million+ expressions to explore, which is why pruning and careful implementation matter.
Interview Verdict: Accepted but slow for large inputs

This approach is correct and foundational but inefficient for large inputs; it helps understand the problem deeply.

🧠
Backtracking with Early Pruning and Long Operand Handling
💡 This approach improves brute force by pruning invalid operands early (leading zeros) and using long integers to avoid overflow, making it more robust and slightly faster.

Intuition

By skipping invalid operands early and carefully managing operand parsing, we reduce unnecessary recursion and avoid errors from integer overflow.

Algorithm

  1. At each recursion, skip operands with leading zeros immediately to avoid invalid expressions.
  2. Use 64-bit integers (long/long long) to parse operands to prevent overflow.
  3. Continue recursive exploration with the same logic as brute force but with these optimizations.
  4. This reduces the search space and prevents runtime errors.
💡 The key improvement is pruning invalid operands early and safe operand parsing, which are common interview pitfalls.
</>
Code
from typing import List

def addOperators(num: str, target: int) -> List[str]:
    res = []
    def backtrack(index: int, path: str, evaluated: int, last_operand: int):
        if index == len(num):
            if evaluated == target:
                res.append(path)
            return
        for i in range(index, len(num)):
            if i != index and num[index] == '0':
                break  # prune leading zero
            curr_str = num[index:i+1]
            curr = int(curr_str)
            if index == 0:
                backtrack(i+1, curr_str, curr, curr)
            else:
                backtrack(i+1, path + '+' + curr_str, evaluated + curr, curr)
                backtrack(i+1, path + '-' + curr_str, evaluated - curr, -curr)
                backtrack(i+1, path + '*' + curr_str, evaluated - last_operand + last_operand * curr, last_operand * curr)
    backtrack(0, '', 0, 0)
    return res

# Driver code
if __name__ == '__main__':
    print(addOperators("105", 5))
    print(addOperators("00", 0))
    print(addOperators("3456237490", 9191))
Line Notes
if i != index and num[index] == '0':Prune operands with leading zeros early to avoid invalid expressions
curr = int(curr_str)Parse operand safely as integer; Python int can handle large numbers
if index == 0:First operand added without operator to start expression
backtrack(i+1, path + '*' + curr_str, evaluated - last_operand + last_operand * curr, last_operand * curr)Handle multiplication precedence by adjusting evaluated value
import java.util.*;

public class ExpressionAddOperators {
    public List<String> addOperators(String num, int target) {
        List<String> res = new ArrayList<>();
        backtrack(res, new StringBuilder(), num, target, 0, 0L, 0L);
        return res;
    }

    private void backtrack(List<String> res, StringBuilder path, String num, int target, int index, long evaluated, long lastOperand) {
        if (index == num.length()) {
            if (evaluated == target) {
                res.add(path.toString());
            }
            return;
        }
        int len = path.length();
        for (int i = index; i < num.length(); i++) {
            if (i != index && num.charAt(index) == '0') break; // prune leading zero
            String currStr = num.substring(index, i + 1);
            long curr = Long.parseLong(currStr);
            if (index == 0) {
                path.append(currStr);
                backtrack(res, path, num, target, i + 1, curr, curr);
                path.setLength(len);
            } else {
                path.append('+').append(currStr);
                backtrack(res, path, num, target, i + 1, evaluated + curr, curr);
                path.setLength(len);

                path.append('-').append(currStr);
                backtrack(res, path, num, target, i + 1, evaluated - curr, -curr);
                path.setLength(len);

                path.append('*').append(currStr);
                backtrack(res, path, num, target, i + 1, evaluated - lastOperand + lastOperand * curr, lastOperand * curr);
                path.setLength(len);
            }
        }
    }

    // Driver code
    public static void main(String[] args) {
        ExpressionAddOperators solver = new ExpressionAddOperators();
        System.out.println(solver.addOperators("105", 5));
        System.out.println(solver.addOperators("00", 0));
        System.out.println(solver.addOperators("3456237490", 9191));
    }
}
Line Notes
if (i != index && num.charAt(index) == '0') break;Prune invalid operands with leading zeros early
long curr = Long.parseLong(currStr);Use long to safely parse large operands
if (index == 0) {First operand added without operator
path.setLength(len);Backtrack by resetting path length after recursion
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Solution {
public:
    vector<string> addOperators(string num, int target) {
        vector<string> res;
        string path;
        backtrack(num, target, 0, 0, 0, path, res);
        return res;
    }

private:
    void backtrack(const string& num, int target, int index, long long evaluated, long long lastOperand, string& path, vector<string>& res) {
        if (index == num.size()) {
            if (evaluated == target) {
                res.push_back(path);
            }
            return;
        }
        int len = path.size();
        for (int i = index; i < num.size(); ++i) {
            if (i != index && num[index] == '0') break; // prune leading zero
            string currStr = num.substr(index, i - index + 1);
            long long curr = stoll(currStr);
            if (index == 0) {
                path += currStr;
                backtrack(num, target, i + 1, curr, curr, path, res);
                path.resize(len);
            } else {
                path += '+' + currStr;
                backtrack(num, target, i + 1, evaluated + curr, curr, path, res);
                path.resize(len);

                path += '-' + currStr;
                backtrack(num, target, i + 1, evaluated - curr, -curr, path, res);
                path.resize(len);

                path += '*' + currStr;
                backtrack(num, target, i + 1, evaluated - lastOperand + lastOperand * curr, lastOperand * curr, path, res);
                path.resize(len);
            }
        }
    }
};

// Driver code
int main() {
    Solution sol;
    vector<string> res = sol.addOperators("105", 5);
    for (auto& expr : res) cout << expr << endl;
    cout << "---" << endl;
    res = sol.addOperators("00", 0);
    for (auto& expr : res) cout << expr << endl;
    cout << "---" << endl;
    res = sol.addOperators("3456237490", 9191);
    for (auto& expr : res) cout << expr << endl;
    return 0;
}
Line Notes
if (i != index && num[index] == '0') break;Prune operands with leading zeros early
long long curr = stoll(currStr);Use 64-bit integer to safely parse operands
if (index == 0) {First operand added without operator
path.resize(len);Backtrack by resetting path length after recursion
var addOperators = function(num, target) {
    const res = [];
    function backtrack(index, path, evaluated, lastOperand) {
        if (index === num.length) {
            if (evaluated === target) res.push(path);
            return;
        }
        for (let i = index; i < num.length; i++) {
            if (i !== index && num[index] === '0') break; // prune leading zero
            const currStr = num.substring(index, i + 1);
            const curr = Number(currStr);
            if (index === 0) {
                backtrack(i + 1, currStr, curr, curr);
            } else {
                backtrack(i + 1, path + '+' + currStr, evaluated + curr, curr);
                backtrack(i + 1, path + '-' + currStr, evaluated - curr, -curr);
                backtrack(i + 1, path + '*' + currStr, evaluated - lastOperand + lastOperand * curr, lastOperand * curr);
            }
        }
    }
    backtrack(0, '', 0, 0);
    return res;
};

// Driver code
console.log(addOperators("105", 5));
console.log(addOperators("00", 0));
console.log(addOperators("3456237490", 9191));
Line Notes
if (i !== index && num[index] === '0') break;Prune invalid operands with leading zeros early
const curr = Number(currStr);Parse operand safely as number
if (index === 0) {First operand added without operator
backtrack(i + 1, path + '*' + currStr, evaluated - lastOperand + lastOperand * curr, lastOperand * curr);Handle multiplication precedence
⏱
Complexity
TimeO(4^n) but with pruning reduces some branches
SpaceO(n) recursion stack and expression building

Pruning leading zeros cuts down invalid paths early, improving runtime but worst case remains exponential.

💡 Pruning can reduce millions of invalid paths, making the solution practical for typical interview inputs.
Interview Verdict: Accepted with better pruning

This approach is more robust and efficient than pure brute force, suitable for interviews.

🧠
Backtracking with StringBuilder / Mutable String for Performance
💡 This approach optimizes string concatenation by using mutable string builders (or equivalent) to reduce overhead, important in languages like Java and C++.

Intuition

Repeated string concatenation is expensive; using a mutable buffer and backtracking by resetting length improves performance.

Algorithm

  1. Use a mutable string builder to build expressions incrementally.
  2. At each recursion, append operand and operator, recurse, then reset builder length to backtrack.
  3. This avoids creating new string objects repeatedly.
  4. Logic for operand parsing, operator insertion, and evaluation remains the same.
💡 This optimization is subtle but important for performance in interviews where string operations dominate runtime.
</>
Code
from typing import List

def addOperators(num: str, target: int) -> List[str]:
    res = []
    path = []
    def backtrack(index: int, evaluated: int, last_operand: int):
        if index == len(num):
            if evaluated == target:
                res.append(''.join(path))
            return
        for i in range(index, len(num)):
            if i != index and num[index] == '0':
                break
            curr_str = num[index:i+1]
            curr = int(curr_str)
            length_before = len(path)
            if index == 0:
                path.append(curr_str)
                backtrack(i+1, curr, curr)
                path[length_before:] = []
            else:
                path.append('+'); path.append(curr_str)
                backtrack(i+1, evaluated + curr, curr)
                path[length_before:] = []

                path.append('-'); path.append(curr_str)
                backtrack(i+1, evaluated - curr, -curr)
                path[length_before:] = []

                path.append('*'); path.append(curr_str)
                backtrack(i+1, evaluated - last_operand + last_operand * curr, last_operand * curr)
                path[length_before:] = []
    backtrack(0, 0, 0)
    return res

# Driver code
if __name__ == '__main__':
    print(addOperators("123", 6))
    print(addOperators("232", 8))
Line Notes
path = []Use list as mutable string builder for efficient concatenation
res.append(''.join(path))Join list elements to form final expression string
length_before = len(path)Record length before adding new operand/operator to backtrack later
path[length_before:] = []Backtrack by removing appended elements efficiently
import java.util.*;

public class ExpressionAddOperators {
    public List<String> addOperators(String num, int target) {
        List<String> res = new ArrayList<>();
        StringBuilder path = new StringBuilder();
        backtrack(res, path, num, target, 0, 0L, 0L);
        return res;
    }

    private void backtrack(List<String> res, StringBuilder path, String num, int target, int index, long evaluated, long lastOperand) {
        if (index == num.length()) {
            if (evaluated == target) {
                res.add(path.toString());
            }
            return;
        }
        int len = path.length();
        for (int i = index; i < num.length(); i++) {
            if (i != index && num.charAt(index) == '0') break;
            String currStr = num.substring(index, i + 1);
            long curr = Long.parseLong(currStr);
            if (index == 0) {
                path.append(currStr);
                backtrack(res, path, num, target, i + 1, curr, curr);
                path.setLength(len);
            } else {
                path.append('+').append(currStr);
                backtrack(res, path, num, target, i + 1, evaluated + curr, curr);
                path.setLength(len);

                path.append('-').append(currStr);
                backtrack(res, path, num, target, i + 1, evaluated - curr, -curr);
                path.setLength(len);

                path.append('*').append(currStr);
                backtrack(res, path, num, target, i + 1, evaluated - lastOperand + lastOperand * curr, lastOperand * curr);
                path.setLength(len);
            }
        }
    }

    // Driver code
    public static void main(String[] args) {
        ExpressionAddOperators solver = new ExpressionAddOperators();
        System.out.println(solver.addOperators("123", 6));
        System.out.println(solver.addOperators("232", 8));
    }
}
Line Notes
StringBuilder path = new StringBuilder();Use mutable string builder to avoid costly string concatenations
int len = path.length();Store length before recursion to backtrack efficiently
path.setLength(len);Reset builder length to backtrack after recursion
res.add(path.toString());Add built expression string to results
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Solution {
public:
    vector<string> addOperators(string num, int target) {
        vector<string> res;
        string path;
        backtrack(num, target, 0, 0, 0, path, res);
        return res;
    }

private:
    void backtrack(const string& num, int target, int index, long long evaluated, long long lastOperand, string& path, vector<string>& res) {
        if (index == num.size()) {
            if (evaluated == target) {
                res.push_back(path);
            }
            return;
        }
        int len = path.size();
        for (int i = index; i < num.size(); ++i) {
            if (i != index && num[index] == '0') break;
            string currStr = num.substr(index, i - index + 1);
            long long curr = stoll(currStr);
            if (index == 0) {
                path += currStr;
                backtrack(num, target, i + 1, curr, curr, path, res);
                path.resize(len);
            } else {
                path += '+' + currStr;
                backtrack(num, target, i + 1, evaluated + curr, curr, path, res);
                path.resize(len);

                path += '-' + currStr;
                backtrack(num, target, i + 1, evaluated - curr, -curr, path, res);
                path.resize(len);

                path += '*' + currStr;
                backtrack(num, target, i + 1, evaluated - lastOperand + lastOperand * curr, lastOperand * curr, path, res);
                path.resize(len);
            }
        }
    }
};

// Driver code
int main() {
    Solution sol;
    vector<string> res = sol.addOperators("123", 6);
    for (auto& expr : res) cout << expr << endl;
    cout << "---" << endl;
    res = sol.addOperators("232", 8);
    for (auto& expr : res) cout << expr << endl;
    return 0;
}
Line Notes
string path;Mutable string used as builder to efficiently append and backtrack
int len = path.size();Store length before recursion to backtrack
path.resize(len);Reset string length to backtrack after recursion
res.push_back(path);Add current expression to results when target matched
var addOperators = function(num, target) {
    const res = [];
    const path = [];
    function backtrack(index, evaluated, lastOperand) {
        if (index === num.length) {
            if (evaluated === target) res.push(path.join(''));
            return;
        }
        for (let i = index; i < num.length; i++) {
            if (i !== index && num[index] === '0') break;
            const currStr = num.substring(index, i + 1);
            const curr = Number(currStr);
            const lengthBefore = path.length;
            if (index === 0) {
                path.push(currStr);
                backtrack(i + 1, curr, curr);
                path.length = lengthBefore;
            } else {
                path.push('+', currStr);
                backtrack(i + 1, evaluated + curr, curr);
                path.length = lengthBefore;

                path.push('-', currStr);
                backtrack(i + 1, evaluated - curr, -curr);
                path.length = lengthBefore;

                path.push('*', currStr);
                backtrack(i + 1, evaluated - lastOperand + lastOperand * curr, lastOperand * curr);
                path.length = lengthBefore;
            }
        }
    }
    backtrack(0, 0, 0);
    return res;
};

// Driver code
console.log(addOperators("123", 6));
console.log(addOperators("232", 8));
Line Notes
const path = [];Use array as mutable string builder for efficient concatenation
res.push(path.join(''));Join array elements to form expression string when adding to results
const lengthBefore = path.length;Store length before recursion to backtrack
path.length = lengthBefore;Reset array length to backtrack after recursion
⏱
Complexity
TimeO(4^n) but faster due to efficient string operations
SpaceO(n) recursion stack and mutable string builder

Using mutable strings reduces overhead of string copying, improving runtime in practice.

💡 This optimization is crucial in languages where string concatenation is costly, like Java and C++.
Interview Verdict: Accepted with performance optimization

This approach is recommended for interviews to demonstrate attention to performance details.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, coding the optimized backtracking with pruning and mutable strings is best to balance clarity and performance.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(4^n)O(n)Yes (deep recursion)YesMention only - never code due to inefficiency
2. Backtracking with PruningO(4^n) but fewer branchesO(n)YesYesGood to code for correctness and pruning demonstration
3. Backtracking with Mutable String BuilderO(4^n) optimizedO(n)YesYesBest approach to code for performance and clarity
💼
Interview Strategy
💡 Use this guide to understand the problem deeply before interviews. Start by clarifying the problem, then explain brute force, optimize step-by-step, and finally code with confidence.

How to Present

Step 1: Clarify input, output, and constraints with the interviewer.Step 2: Describe the brute force recursive approach to try all operator insertions.Step 3: Explain how to track evaluation and handle multiplication precedence.Step 4: Discuss pruning invalid operands with leading zeros.Step 5: Mention performance optimizations like using mutable strings.Step 6: Code the solution carefully, testing edge cases.

Time Allocation

Clarify: 3min → Approach: 7min → Code: 15min → Test: 5min. Total ~30min

What the Interviewer Tests

The interviewer tests your understanding of recursion, backtracking, operator precedence, pruning invalid inputs, and coding correctness.

Common Follow-ups

  • What if division operator is added? → Need to handle division carefully with integer division and zero division checks.
  • How to handle very large numbers? → Use long integers or BigInteger types to avoid overflow.
💡 These follow-ups test your ability to extend the solution and handle edge cases beyond the base problem.
🔍
Pattern Recognition

When to Use

Use this pattern when you need to generate all expressions by inserting operators between digits to meet a target, especially when operator precedence matters.

Signature Phrases

add binary operatorsevaluate expression to targetno leading zeros

NOT This Pattern When

This is not a dynamic programming problem because it requires generating all valid expressions, not just counting or optimizing.

Similar Problems

Different Ways to Add Parentheses - explores all ways to parenthesize expressionsBasic Calculator II - evaluates expressions with operator precedenceRestore IP Addresses - backtracking with substring partitioning and validation

Practice

(1/5)
1. Given the following Python code snippet implementing the optimized trie approach, what is the output of maximizeXor(nums, queries) for nums = [0,1,2] and queries = [[3,1],[1,2]]?
class TrieNode:
    def __init__(self):
        self.children = [null, null]

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, num):
        node = self.root
        for i in range(31, -1, -1):
            bit = (num >> i) & 1
            if not node.children[bit]:
                node.children[bit] = TrieNode()
            node = node.children[bit]

    def maxXor(self, num):
        node = self.root
        if not node.children[0] and not node.children[1]:
            return -1
        max_xor = 0
        for i in range(31, -1, -1):
            bit = (num >> i) & 1
            toggled_bit = 1 - bit
            if node.children[toggled_bit]:
                max_xor |= (1 << i)
                node = node.children[toggled_bit]
            else:
                node = node.children[bit]
        return max_xor

def maximizeXor(nums, queries):
    nums.sort()
    queries = sorted([(x, m, i) for i, (x, m) in enumerate(queries)], key=lambda x: x[1])
    res = [-1] * len(queries)
    trie = Trie()
    idx = 0
    n = len(nums)
    for x, m, i in queries:
        while idx < n and nums[idx] <= m:
            trie.insert(nums[idx])
            idx += 1
        res[i] = trie.maxXor(x)
    return res

print(maximizeXor(nums, queries))
easy
A. [3, 3]
B. [2, 3]
C. [3, 1]
D. [-1, 3]

Solution

  1. Step 1: Process first query (3,1)

    Only nums ≤ 1 are [0,1]. Insert 0 and 1 into trie. maxXor(3) with {0,1} is max(3^0=3,3^1=2)=3.
  2. Step 2: Process second query (1,2)

    Now nums ≤ 2 are [0,1,2]. Insert 2. maxXor(1) with {0,1,2} is max(1^0=1,1^1=0,1^2=3)=3.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Results match expected XOR values [OK]
Hint: Insert nums ≤ m incrementally, then maxXor query [OK]
Common Mistakes:
  • Forgetting to sort queries and nums
  • Not inserting all eligible nums before query
  • Returning -1 when trie is empty incorrectly
2. Given the following code snippet implementing the optimal approach to find the median of two sorted arrays, what is the returned median for input nums1 = [1, 3], nums2 = [2]?
easy
A. 1.5
B. 2.5
C. 2.0
D. 3.0

Solution

  1. Step 1: Trace initial variables

    nums1=[1,3], nums2=[2], m=2, n=1, swap occurs so nums1=[2], nums2=[1,3], m=1, n=2, half_len=2.
  2. Step 2: First iteration of binary search

    low=0, high=1, i=0, j=2-0=2; max_left_nums1=-inf, min_right_nums1=2; max_left_nums2=nums2[1]=3, min_right_nums2=inf. Condition fails, low updated to i+1=1.
  3. Step 3: Second iteration

    i=1, j=1; max_left_nums1=2, min_right_nums1=inf; max_left_nums2=nums2[0]=1, min_right_nums2=nums2[1]=3. Condition satisfied.
  4. Step 4: Calculate median

    Total length is odd (3), median is max(max_left_nums1, max_left_nums2) = max(2,1) = 2.0.
  5. Final Answer:

    Option C -> Option C
  6. Quick Check:

    Median of [1,2,3] is 2.0 [OK]
Hint: Swap arrays to binary search smaller one; median is 2.0 [OK]
Common Mistakes:
  • Not swapping arrays leads to wrong partition
  • Off-by-one in i or j calculation
  • Confusing odd/even median calculation
3. Given a list of unique words, you need to find all pairs of distinct indices (i, j) such that the concatenation of words[i] + words[j] forms a palindrome. Which approach guarantees an optimal solution in terms of time complexity for large inputs?
easy
A. Brute force nested loops checking all pairs and verifying palindrome by concatenation
B. Building a Trie of reversed words and tracking palindrome suffixes to find pairs efficiently
C. Dynamic programming to find palindromic substrings and then pairing indices accordingly
D. Greedy approach by sorting words and pairing those with matching prefixes and suffixes

Solution

  1. Step 1: Understand brute force limitations

    Brute force checks all pairs, resulting in O(n^2 * k) time, which is inefficient for large n.
  2. Step 2: Recognize Trie-based optimization

    Building a Trie of reversed words allows prefix matching and palindrome suffix tracking, reducing redundant checks and achieving O(n * k^2) time.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Trie-based approach is known optimal for palindrome pairs [OK]
Hint: Trie with palindrome suffixes optimizes palindrome pair search [OK]
Common Mistakes:
  • Assuming brute force is acceptable for large inputs
  • Confusing palindrome substring DP with pair finding
  • Believing greedy prefix-suffix matching suffices
4. Given the following code snippet implementing the two-pointer approach for trapping rain water, what is the value of the variable water after the first iteration of the while loop when the input is [2, 0, 2]?
easy
A. 0
B. 2
C. 1
D. 3

Solution

  1. Step 1: Initialize variables

    left=0, right=2, left_max=0, right_max=0, water=0
  2. Step 2: First iteration of while loop

    height[left]=2, height[right]=2, since height[left] is not less than height[right], else branch runs. height[right]=2 ≥ right_max=0, so right_max=2, right decrements to 1, water remains 0.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Water not added on first iteration because max updated [OK]
Hint: Water added only when current height < max height on that side [OK]
Common Mistakes:
  • Adding water before max height is updated
  • Confusing left and right pointer conditions
  • Off-by-one errors in pointer movement
5. What is the time complexity of the bitmask backtracking solution for the N-Queens problem, and why is it not simply O(n^n)?
medium
A. O(n^n) because each row tries all columns independently
B. O(2^n) because bitmasking iterates over all subsets of columns
C. O(n!) because each queen placement reduces available columns and pruning avoids invalid states
D. O(n^2) because the board is of size nxn and each cell is checked

Solution

  1. Step 1: Understand pruning effect on search space

    Each queen placed reduces available columns, so permutations of columns are tried, not all combinations.
  2. Step 2: Recognize factorial complexity

    Backtracking tries permutations of n columns for n rows, leading to O(n!) complexity, not exponential or polynomial.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Pruning reduces search to permutations, not all subsets [OK]
Hint: N-Queens backtracking explores permutations -> O(n!) [OK]
Common Mistakes:
  • Confusing subsets (2^n) with permutations (n!)
  • Assuming polynomial due to board size
  • Ignoring pruning effect
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard