Expression Add Operators asks you to slot +, -, and * between the digits of a string so the expression evaluates to a target. It is a classic backtracking problem with one nasty twist: multiplication breaks the simple running-total trick, so you have to carry an extra piece of state.
Problem. Given a string num of digits and an integer target, return all expressions you can build by
inserting +, -, or * between the digits (digits may also be joined into multi-digit numbers) so the
expression equals target.
Example: num = "123", target = 6 → ["1+2+3", "1*2*3"] (both evaluate to 6).
The slow way first
You could generate every possible expression as a string, then evaluate each one. With n digits there are 3 choices in each of the n - 1 gaps, so that is exponential — and re-evaluating each full string from scratch is wasteful. We want to compute the value as we build, and prune nothing we do not have to.
The real question: can I keep a running total as I add each operand? For + and -, yes. For *, no — because * binds tighter than the +/- already baked into the total.
The idea: carry total and prev
Walk the string left to right. At each position pick the next operand (a slice of digits), then branch on the operator:
+ operand→total + operand, and the newprevis+operand.- operand→total - operand, and the newprevis-operand.* operand→ here is the trick. We must undo the last operand from the total and re-add it multiplied:total - prev + prev * operand. The newprevisprev * operand.
prev is the value the previous operand contributed, so * can roll it back and replace it with the product.
When we reach the end of the string, if total equals target we record the expression we built.
Walk through it
Step through the animation on num = "123", target 6. We take 1, then choose * twice. Watch prev lead the way: after 1*2, total and prev are both 2; after 1*2*3, the * undoes prev (2) and re-adds 2*3 = 6, so total becomes 6. At the end of the string, total equals target, so 1*2*3 is recorded.
Pseudocode
dfs(index, expr, total, prev):
if index == end of string:
if total == target: record expr
return
for each operand slice starting at index:
cur = value of slice
if index == 0: # first operand: no operator
dfs(next, slice, cur, cur)
else:
dfs(next, expr + "+" + slice, total + cur, cur)
dfs(next, expr + "-" + slice, total - cur, -cur)
dfs(next, expr + "*" + slice, total - prev + prev*cur, prev*cur)The Python solution
def add_operators(num, target):
res = []
def dfs(i, expr, total, prev):
if i == len(num):
if total == target:
res.append(expr)
return
for j in range(i, len(num)):
cur = int(num[i:j + 1])
s = num[i:j + 1]
if i == 0:
dfs(j + 1, s, cur, cur)
else:
dfs(j + 1, expr + "+" + s, total + cur, cur)
dfs(j + 1, expr + "-" + s, total - cur, -cur)
dfs(j + 1, expr + "*" + s, total - prev + prev * cur, prev * cur)
dfs(0, "", 0, 0)
return resdfs(i, expr, total, prev)tracks where we are, the string so far, the running value, and the previous operand contribution.- The base case (line 4) fires at the end of the string; we record
expronly iftotal == target. - The inner
for jloop picks the next operandcur, allowing multi-digit numbers like"12". i == 0is the first operand, which takes no operator —prevstarts equal tocur.- Line 17 is the heart:
*undoesprevfrom the total and re-addsprev * cur, and passesprev * curas the newprevso a later*can chain correctly.
Complexity
| Case | Time | Notes |
|---|---|---|
| Number of expressions | O(4^n) (moderate) | 3 operators plus join, at each gap |
| Work per expression | O(n) (moderate) | building/recording the string |
O(n) (moderate)There are n - 1 gaps and roughly four choices at each (+, -, *, or extend the number), so the search tree is about O(4^n). Each leaf does O(n) work to assemble its string. Recursion depth — and the expression string — is O(n).
When this pattern shows up
Whenever a problem says "insert operators / split a string / try all combinations and evaluate,"
reach for DFS with backtracking. The signature move here is carrying a small amount of extra state (prev)
so an operator that binds tighter can be applied correctly without re-parsing.
Two traps. First, multiplication: you must pass prev * cur (not cur) as the new prev, or chained
* will be wrong. Second, leading zeros: a multi-digit operand like "05" is invalid, so skip a slice
that starts with 0 and has length greater than 1.
Practice
After building 1*2 (total = 2, prev = 2), we choose * with the next operand 3. What are the new total and prev?
1. Why does multiplication need the prev value, unlike + and -?
2. After a * step, what is passed as the new prev?
3. How is the first operand handled differently?
4. Why must we skip operand slices that start with 0 and have length over 1?