Expression Add Operators: The Multiplication Precedence Trick
Advertisement
Problem Statement
You are given a digit string num and an integer target. Insert any combination of binary operators +, -, * between digits to form arithmetic expressions that evaluate to target. Return all such expressions.
For num = "123" and target = 6, the answer is ["1+2+3", "1*2*3"]. For num = "232" and target = 8, the answer is ["2*3+2", "2+3*2"]. The classic gotcha: operands with leading zeros (like 05) are forbidden, and standard operator precedence applies, so multiplication must override the running sum.
Why This Problem Matters
LeetCode 282 is a hard-tier interview favorite at Meta, Google, and Amazon for senior IC roles. It tests three things at once: enumerating combinations of digit splits and operators (a 4-way decision tree), correctly modeling operator precedence inside a recursive evaluator, and pruning leading-zero operands. Candidates who can write this clean in 25 minutes signal strong recursion fundamentals plus the rare ability to handle stateful expression evaluation without parsing into an AST.
The Core Insight (decision tree / state space)
At each step of the recursion we are sitting at index idx and asking: "What is the next operand?" That operand can be 1, 2, or up to n - idx digits long. Once we pick the operand, we must decide which operator binds it to the running expression — +, -, or *. So at each gap between digits we have roughly 4 branches: pick a length, then pick one of three operators (the first operand has no operator).
The trick is multiplication precedence. When we choose *, we cannot just multiply into the accumulator, because the previous addition has already committed. Instead we maintain a last variable holding the value of the most recently added operand. To multiply, we undo that addition and redo it as a multiplication:
new_eval = eval - last + last * current
new_last = last * currentFor + current we have new_eval = eval + current, new_last = current. For - current we have new_eval = eval - current, new_last = -current. The negative last is essential — if the next operator is *, we still want to multiply against the signed operand.
Visual Dry Run (recursion tree)
For num = "232", target = 8:
bt(idx=0, path="", eval=0, last=0)
/ | \
pick "2" pick "23" pick "232"
| ... (leaf, eval=232 != 8)
bt(1, "2", 2, 2)
/ | \
+"3" -"3" *"3"
bt(2,"2+3", bt(2,"2-3", bt(2,"2*3",
5, 3) -1, -3) 6, 6)
/ | \ ... / | \
+2 -2 *2 +2 -2 *2
"2+3+2" ... "2*3+2" ...
eval=7 eval=8 -> MATCHThe branch 2*3+2 evaluates to 8 because we computed eval=6, last=6 after the multiplication, then did eval = 6 + 2 = 8. The branch 2+3*2 works because at the * step we did eval = 5 - 3 + 3*2 = 8. That is the precedence trick in action.
Solution (Optimal) — Python + JavaScript with backtracking template, complexity
def addOperators(num, target):
n, result = len(num), []
def backtrack(idx, path, eval_val, last):
if idx == n:
if eval_val == target:
result.append(path)
return
for i in range(idx, n):
seg = num[idx:i + 1]
if len(seg) > 1 and seg[0] == '0':
break # leading zero pruning
val = int(seg)
if idx == 0:
backtrack(i + 1, seg, val, val)
else:
backtrack(i + 1, path + '+' + seg, eval_val + val, val)
backtrack(i + 1, path + '-' + seg, eval_val - val, -val)
backtrack(i + 1, path + '*' + seg,
eval_val - last + last * val, last * val)
backtrack(0, '', 0, 0)
return resultfunction addOperators(num, target) {
const n = num.length;
const result = [];
const backtrack = (idx, path, evalVal, last) => {
if (idx === n) {
if (evalVal === target) result.push(path);
return;
}
for (let i = idx; i < n; i++) {
const seg = num.slice(idx, i + 1);
if (seg.length > 1 && seg[0] === '0') break;
const val = Number(seg);
if (idx === 0) {
backtrack(i + 1, seg, val, val);
} else {
backtrack(i + 1, path + '+' + seg, evalVal + val, val);
backtrack(i + 1, path + '-' + seg, evalVal - val, -val);
backtrack(i + 1, path + '*' + seg,
evalVal - last + last * val, last * val);
}
}
};
backtrack(0, '', 0, 0);
return result;
}Complexity: time is O(n times 4^n) — three operator choices per gap, n gaps, and string concatenation per call costs O(n). Space is O(n) recursion depth plus the output. For JavaScript, prefer BigInt if the digit string is long enough to overflow a 53-bit Number.
Common Mistakes
- Forgetting the
lastvariable and applying*directly toeval. This silently breaks2+3*2style cases. - Allowing operands with leading zeros. Always
break(notcontinue) once you detectseg[0] == '0'andlen(seg) > 1— a longer chunk with the same prefix is also invalid. - Using
inttypes in languages where intermediate products can overflow. Uselong/long long/BigIntdefensively. - Making
lastunsigned. After a-,lastmust be negative so the next*multiplies the right sign.
Interview Tips
- State the precedence trick out loud first — interviewers want to hear "I will track the last operand to undo and redo for multiplication."
- Mention the 4^n upper bound and explain why it is tight in adversarial inputs.
- Walk through
"232"on the board; it is the smallest case that exercises both+and*paths. - Discuss the leading-zero break as a pruning step, not an afterthought.
Follow-up Questions
- Allow division. How does the trick change? (You need rationals, not just
last.) - What if you must use exactly
koperators? Add a counter to the state. - Return the count of valid expressions instead of the list — same recursion, drop the path string.
- Different Ways to Add Parentheses (LeetCode 241) — divide-and-conquer cousin.
Key Takeaways
- The multiplication precedence trick —
eval - last + last * val— is the heart of LeetCode 282. - Backtracking enumerates operand splits and operator choices; the decision tree has roughly 4^n leaves.
- Leading-zero pruning uses
break, notcontinue, because longer segments share the bad prefix. - Carry the signed
lastacross-operations so subsequent multiplications stay correct. - This problem is a hard-tier interview staple at FAANG; mastering it unlocks calculator-style follow-ups.
- The same state-machine pattern (eval, last, path) generalizes to many expression-evaluation problems.
Sources:
Advertisement