Skip to content

43. Multiply Strings (Medium)

Problem

Given two non-negative integers num1 and num2 represented as strings, return their product as a string. You must not convert to integers directly (simulate the arithmetic).

Example

  • num1 = "2", num2 = "3""6"
  • num1 = "123", num2 = "456""56088"

LeetCode 43 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Big-integer via int() (cheating)

Not permitted by the problem, but shown for contrast.

def multiply(num1, num2):
return str(int(num1) * int(num2))

Complexity

  • Time: O(nm)O(n · m) ish.
  • Space: O(n+m)O(n + m).

Approach 2: Schoolbook multiplication on digit arrays (canonical)

result[i + j + 1] accumulates num1[i] * num2[j] with carries propagated at the end.

def multiply(num1, num2):
if num1 == "0" or num2 == "0": # L1: O(1) early exit
return "0"
n, m = len(num1), len(num2) # L2: O(1)
result = [0] * (n + m) # L3: O(n + m)
for i in range(n - 1, -1, -1): # L4: outer loop, n iterations
for j in range(m - 1, -1, -1): # L5: inner loop, m iterations
prod = int(num1[i]) * int(num2[j]) # L6: O(1)
p1, p2 = i + j, i + j + 1 # L7: O(1)
total = prod + result[p2] # L8: O(1)
result[p2] = total % 10 # L9: O(1)
result[p1] += total // 10 # L10: O(1)
# Strip leading zeros
start = 0
while start < len(result) and result[start] == 0:
start += 1 # L11: O(n + m)
return "".join(map(str, result[start:])) # L12: O(n + m)

Where the time goes, line by line

Variables: n = len(num1), m = len(num2).

LinePer-call costTimes executedContribution
L3 (init result)O(1)O(1)n + mO(n+m)O(n + m)
L4, L5 (nested loops)O(1)O(1)n * mO(nm)O(n · m) ← dominates
L6-L10 (digit multiply + carry)O(1)O(1)n * mO(nm)O(n · m)
L11-L12 (strip + join)O(1)O(1)n + mO(n+m)O(n + m)

Every pair of digit positions (i, j) is visited exactly once; there are n * m such pairs.

Complexity

  • Time: O(nm)O(n · m), driven by L4/L5/L6-L10 (the nested digit-multiply loop).
  • Space: O(n+m)O(n + m) for the result array.

Why p1, p2 = i + j, i + j + 1 works

In schoolbook multiplication, the product of two digits at positions i (from num1) and j (from num2) contributes to positions i + j (carry) and i + j + 1 (units) of the result. Carries propagate leftward during accumulation.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 3: Karatsuba (divide and conquer)

O(nlog23)O(n^{log₂3})O(n1.585)O(n^{1.585}). Rarely worth the code in an interview but known as “the first better-than-quadratic integer multiplication.”

The trick: split each number in half (x = x_hi · 10^m + x_lo, similarly for y). Schoolbook would do four multiplications:

xy = x_hi y_hi · 10^(2m) + (x_hi y_lo + x_lo y_hi) · 10^m + x_lo y_lo

Karatsuba does three by computing M = (x_hi + x_lo)(y_hi + y_lo) and recovering the cross terms as M - x_hi y_hi - x_lo y_lo.

def multiply(num1, num2):
if num1 == "0" or num2 == "0":
return "0"
def add_strs(a, b):
i, j, carry, out = len(a) - 1, len(b) - 1, 0, []
while i >= 0 or j >= 0 or carry:
x = int(a[i]) if i >= 0 else 0
y = int(b[j]) if j >= 0 else 0
total = x + y + carry
out.append(str(total % 10)); carry = total // 10
i -= 1; j -= 1
return "".join(reversed(out))
def sub_strs(a, b): # a >= b
i, j, borrow, out = len(a) - 1, len(b) - 1, 0, []
while i >= 0:
x = int(a[i]) - borrow
y = int(b[j]) if j >= 0 else 0
if x < y: x += 10; borrow = 1
else: borrow = 0
out.append(str(x - y))
i -= 1; j -= 1
return "".join(reversed(out)).lstrip("0") or "0"
def shift(s, n):
return s + "0" * n if s != "0" else "0"
def kar(x, y):
if len(x) == 1 and len(y) == 1:
return str(int(x) * int(y))
if x == "0" or y == "0":
return "0"
n = max(len(x), len(y))
m = n // 2
x_hi = x[:-m] if len(x) > m else "0"
x_lo = x[-m:].lstrip("0") or "0"
y_hi = y[:-m] if len(y) > m else "0"
y_lo = y[-m:].lstrip("0") or "0"
z2 = kar(x_hi, y_hi) # x_hi · y_hi
z0 = kar(x_lo, y_lo) # x_lo · y_lo
z1 = sub_strs(kar(add_strs(x_hi, x_lo), add_strs(y_hi, y_lo)),
add_strs(z2, z0)) # cross term
result = add_strs(shift(z2, 2 * m), shift(z1, m))
result = add_strs(result, z0)
return result.lstrip("0") or "0"
return kar(num1, num2)

The recursion does T(n) = 3T(n/2) + O(n)O(n), which solves to O(nlog23)O(n^{log₂3}). Helpers do string-level addition and subtraction so we never cast the full input to int.

Complexity

  • Time: O(n1.585)O(n^{1.585}).
  • Space: O(n)O(n) for the recursion stack and intermediate string results.

Summary

ApproachTimeSpace
Cast to intO(n+m)O(n + m)O(n+m)O(n + m)
SchoolbookO(nm)O(n · m)O(n+m)O(n + m)
KaratsubaO(n1.585)O(n^{1.585})O(n)O(n)

Schoolbook is the canonical interview answer. Know the i + j + 1 positional index.

Test cases

# Quick smoke tests, paste into a REPL or save as test_043.py and run.
# Uses the canonical implementation (Approach 2: schoolbook multiplication).
def multiply(num1, num2):
if num1 == "0" or num2 == "0":
return "0"
n, m = len(num1), len(num2)
result = [0] * (n + m)
for i in range(n - 1, -1, -1):
for j in range(m - 1, -1, -1):
prod = int(num1[i]) * int(num2[j])
p1, p2 = i + j, i + j + 1
total = prod + result[p2]
result[p2] = total % 10
result[p1] += total // 10
start = 0
while start < len(result) and result[start] == 0:
start += 1
return "".join(map(str, result[start:]))
def _run_tests():
assert multiply("2", "3") == "6"
assert multiply("123", "456") == "56088"
assert multiply("0", "12345") == "0"
assert multiply("99", "99") == "9801"
assert multiply("1", "1") == "1"
assert multiply("9999", "9999") == "99980001"
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Math and Number Theory, the arithmetic invariant behind digits, divisibility, modulo behavior, and identities.
  • Simulation, the explicit state model for executing rules exactly while keeping cases organized.