Problem Overview — Multiply Two Non-Negative Integer Strings Without BigInteger
LeetCode 43 Multiply Strings gives you two non-negative integers num1 and num2 represented as strings and asks you to return their product, also as a string. The key constraint: you must not use any built-in BigInteger library or convert the inputs directly to integers. The numbers can be extremely large — up to 200 digits each — far exceeding what any native 64-bit integer can hold.
This problem is the multiplication analogue of Add Binary and Plus One: instead of adding two numbers, you multiply them using the same digit-by-digit approach taught in elementary school. The challenge is organizing the partial products correctly so they accumulate at the right positions in the result without overflow.
Understanding the position mapping — which positions in the result a product digit contributes to — is the entire key to the solution. Once you internalize the index formula, the rest is mechanical: a nested loop for products, a carry pass, and a leading-zero strip.
- 1 ≤ num1.length, num2.length ≤ 200
- num1 and num2 consist of digits only (0–9)
- Both num1 and num2 do not have leading zeros, except the number 0 itself
- Must not use built-in BigInteger library
- Must not convert inputs directly to integers
- Return the product as a string
Grade-School Algorithm — Digit-by-Digit Multiplication with Positional Accumulation
Grade-school multiplication works by multiplying each digit of one number by each digit of the other, shifting the partial products by the appropriate power of ten, and summing all partial products. For two numbers with m and n digits respectively, this produces up to m*n individual digit products, each contributing to a specific position in the result.
The critical insight is the position mapping: when you multiply the digit at index i of num1 (from the left) by the digit at index j of num2, the resulting value contributes to positions i+j and i+j+1 in a result array of size m+n. The lower-order contribution lands at i+j+1 and the higher-order carry lands at i+j.
Rather than computing and shifting separate partial products as you do on paper, you accumulate all contributions directly into a single result array. This avoids the intermediate addition step entirely and makes the algorithm both elegant and efficient.
The Key Position Formula — i+j and i+j+1
The digit at index i of num1 multiplied by the digit at index j of num2 contributes to positions i+j+1 (the ones place of that product) and i+j (where any carry lands). This mapping holds because the digit at index i is offset i positions from the most significant end, so the product occupies the corresponding offset in a result array of size m+n. Memorize this formula and the rest of the algorithm follows directly.
Result Array Setup — Size m+n Holds Any Product of m and n Digit Numbers
The product of an m-digit number and an n-digit number has at most m+n digits. This is because the maximum m-digit number is 10^m - 1 and the maximum n-digit number is 10^n - 1, and their product is less than 10^(m+n). Allocating a result array of exactly size m+n guarantees it can hold any possible product without overflow.
Initialize the result array with all zeros. As you iterate over every pair of digit indices (i, j), compute the product of the two digits and add it to result[i+j+1]. You do not process carries during this accumulation phase — you simply let the values at each position grow as large as they need to. After all pairs are processed, a single right-to-left pass handles all the carries at once.
This two-phase approach — accumulate first, carry later — is what makes the algorithm clean. It decouples the product computation from carry propagation, eliminating any risk of a carry at one position cascading and corrupting a position you have not yet accumulated into.
- 1Let m = num1.length, n = num2.length
- 2Create result array of size m + n, initialized to all zeros
- 3Outer loop: iterate i from 0 to m-1 (indices into num1)
- 4Inner loop: iterate j from 0 to n-1 (indices into num2)
- 5Compute product = digit1 * digit2 and add to result[i+j+1]
- 6After nested loops complete, process carries in a single right-to-left pass
- 7Strip any leading zeros from the front of the result array
- 8Join remaining digits into a string and return
Position-Based Accumulation — Collect All Products Before Handling Carries
During the accumulation phase, result[i+j+1] can grow well beyond 9. For example, if both digits are 9, their product is 81 — but the slot may have received contributions from many other digit pairs as well. A result slot can hold a value up to 9*9*min(m,n) before the carry pass. This is fine; the carry pass normalizes everything afterward.
After accumulating all products, iterate the result array from right to left (from index m+n-1 down to index 1). At each index k, compute carry = result[k] / 10 (integer division) and remainder = result[k] % 10. Set result[k] = remainder and add carry to result[k-1]. This single pass is sufficient because carries can only propagate left, and we are already moving left.
After the carry pass, result[0] may still be 0 (if the total product requires fewer than m+n digits). Strip any leading zeros from the front. The only special case: if all remaining digits are 0, the result should be "0" not "" — ensure at least one digit remains after stripping.
Accumulate First, Carry Later — One Pass Handles All Carries
Accumulating all products before processing carries avoids nested carry propagation. If you tried to normalize after each digit pair, a carry at position k could affect position k-1, which might not have received all its contributions yet. By deferring all carry processing to a single right-to-left pass after the full accumulation, you guarantee correctness regardless of how large any slot grows during the accumulation phase.
Strip Leading Zeros — Handle "0" Times Anything and Short Products
After the carry pass, the result array may begin with one or more zeros. This happens whenever the product requires fewer than m+n digits — for example, 12 * 34 = 408 has 3 digits, not 4. The result array of size 4 will have a leading zero at index 0 after the carry pass. Strip these leading zeros before joining.
The stripping logic: find the first non-zero index in the result array and slice from there. In Python: "".join(str(d) for d in result).lstrip("0") or "0". In Java: use a StringBuilder, skip leading zeros, then append remaining digits. If the entire array is zeros, return "0".
A special case worth testing: "0" * anything = "0". After the accumulation and carry pass, the result array will be all zeros. The stripping logic must return "0" rather than an empty string. Most implementations handle this naturally by using "0" as the fallback when stripping removes all digits.
- 1After carry pass, find the index of the first non-zero digit in result array
- 2If no non-zero digit exists (entire array is zero), return "0"
- 3Otherwise join all digits from the first non-zero index onward into a string
- 4Return the resulting string — this is the product
- 5Test with "0" * "0" = "0", "1" * "0" = "0", "123" * "1" = "123"
Code Walkthrough — Python and Java with O(m*n) Time O(m+n) Space
Python solution: def multiply(num1, num2): m, n = len(num1), len(num2); res = [0] * (m + n); for i in range(m - 1, -1, -1): for j in range(n - 1, -1, -1): p = (ord(num1[i]) - ord("0")) * (ord(num2[j]) - ord("0")); res[i+j+1] += p; for k in range(m+n-1, 0, -1): res[k-1] += res[k] // 10; res[k] %= 10; return "".join(map(str, res)).lstrip("0") or "0". The nested loop iterates from the end of both strings since digits at higher indices are less significant.
Java solution: public String multiply(String num1, String num2) { int m = num1.length(), n = num2.length(); int[] res = new int[m + n]; for (int i = m - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { int p = (num1.charAt(i) - '0') * (num2.charAt(j) - '0'); res[i+j+1] += p; } } for (int k = m+n-1; k > 0; k--) { res[k-1] += res[k] / 10; res[k] %= 10; } StringBuilder sb = new StringBuilder(); for (int d : res) if (sb.length() > 0 || d != 0) sb.append(d); return sb.length() == 0 ? "0" : sb.toString(); }
Time complexity: O(m*n) for the nested product loop, where m and n are the lengths of the two strings. The carry pass and string join are both O(m+n). Space complexity: O(m+n) for the result array. Both solutions are clean, handle all edge cases including zero inputs, and require no BigInteger or integer conversion.
Iterate From the End — Least Significant Digit Is at the Highest Index
When iterating through the digit strings, start from the last index (the ones digit) and work toward index 0 (the most significant digit). The position formula i+j+1 assumes i and j measure distance from the most significant end — which is correct when i=0 is the leftmost character. Iterating from the end is natural because that is where multiplication starts, and ord(char) - ord("0") converts the character to its integer value without any base conversion.