questions M-R
This commit is contained in:
211
backend/data/questions/multiply-strings.yaml
Normal file
211
backend/data/questions/multiply-strings.yaml
Normal file
@@ -0,0 +1,211 @@
|
||||
title: Multiply Strings
|
||||
slug: multiply-strings
|
||||
difficulty: medium
|
||||
leetcode_id: 43
|
||||
leetcode_url: https://leetcode.com/problems/multiply-strings/
|
||||
categories:
|
||||
- strings
|
||||
- math
|
||||
patterns:
|
||||
- two-pointers
|
||||
|
||||
description: |
|
||||
Given two non-negative integers `num1` and `num2` represented as strings, return the product of `num1` and `num2`, also represented as a string.
|
||||
|
||||
**Note:** You must not use any built-in BigInteger library or convert the inputs to integer directly.
|
||||
|
||||
constraints: |
|
||||
- `1 <= num1.length, num2.length <= 200`
|
||||
- `num1` and `num2` consist of digits only
|
||||
- Both `num1` and `num2` do not contain any leading zero, except the number `0` itself
|
||||
|
||||
examples:
|
||||
- input: 'num1 = "2", num2 = "3"'
|
||||
output: '"6"'
|
||||
explanation: "2 × 3 = 6"
|
||||
- input: 'num1 = "123", num2 = "456"'
|
||||
output: '"56088"'
|
||||
explanation: "123 × 456 = 56088"
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Think back to how you learned multiplication in primary school — the "long multiplication" method. When you multiply two multi-digit numbers by hand, you don't compute the entire product at once. Instead, you multiply each digit of one number by each digit of the other, keeping track of where each partial product should go based on its position (tens, hundreds, thousands, etc.).
|
||||
|
||||
The key insight is that when you multiply the digit at position `i` of one number with the digit at position `j` of another, the result contributes to positions `i + j` and `i + j + 1` in the final answer. This is because:
|
||||
- A digit at position `i` (from the right, 0-indexed) represents a value of `digit × 10^i`
|
||||
- Multiplying two such digits gives a result that spans two positions in the output
|
||||
|
||||
For example, multiplying `123 × 456`:
|
||||
- `3 × 6 = 18` → contributes to positions 0 and 1 (ones and tens)
|
||||
- `2 × 6 = 12` → contributes to positions 1 and 2 (tens and hundreds)
|
||||
- And so on...
|
||||
|
||||
By accumulating all these partial products into an array and handling carries, we simulate exactly what happens on paper.
|
||||
|
||||
approach: |
|
||||
We simulate grade-school multiplication using an array to accumulate partial products:
|
||||
|
||||
**Step 1: Handle the zero case**
|
||||
|
||||
- If either `num1` or `num2` is `"0"`, return `"0"` immediately
|
||||
- This avoids unnecessary computation and handles the edge case cleanly
|
||||
|
||||
|
||||
|
||||
**Step 2: Initialise the result array**
|
||||
|
||||
- Create an array of size `len(num1) + len(num2)` filled with zeros
|
||||
- The maximum length of the product of two numbers is the sum of their lengths (e.g., `99 × 99 = 9801` has 4 digits, and `len("99") + len("99") = 4`)
|
||||
|
||||
|
||||
|
||||
**Step 3: Multiply digit by digit**
|
||||
|
||||
- Iterate through `num1` from right to left (index `i`)
|
||||
- For each digit in `num1`, iterate through `num2` from right to left (index `j`)
|
||||
- Convert characters to integers: `digit1 = int(num1[i])`, `digit2 = int(num2[j])`
|
||||
- Compute the product: `product = digit1 × digit2`
|
||||
|
||||
|
||||
|
||||
**Step 4: Accumulate into the correct positions**
|
||||
|
||||
- The positions in the result array for this product are `i + j` and `i + j + 1`
|
||||
- Add the product to what's already at position `i + j + 1` (the lower position)
|
||||
- Handle the carry: `result[i + j + 1] = sum % 10`, and add `sum // 10` to `result[i + j]`
|
||||
|
||||
|
||||
|
||||
**Step 5: Convert array to string**
|
||||
|
||||
- Join the digits in the result array
|
||||
- Strip any leading zeros (but keep at least one digit if result is zero)
|
||||
|
||||
common_pitfalls:
|
||||
- title: Using Built-in Conversion
|
||||
description: |
|
||||
The problem explicitly forbids converting strings to integers directly using `int()` or similar. Solutions like `str(int(num1) * int(num2))` violate the constraints.
|
||||
|
||||
The constraint exists because the input numbers can have up to 200 digits — far exceeding the range of standard 64-bit integers (`2^63 - 1` has only 19 digits). This problem tests whether you can implement arbitrary-precision arithmetic.
|
||||
wrong_approach: "int(num1) * int(num2)"
|
||||
correct_approach: "Simulate digit-by-digit multiplication"
|
||||
|
||||
- title: Off-by-One Position Errors
|
||||
description: |
|
||||
When multiplying digit at index `i` with digit at index `j`, remember that indices are from the left but place values are from the right.
|
||||
|
||||
If you iterate left-to-right but use `i + j` directly, you'll place digits in the wrong positions. Either reverse the strings first, or use `(len1 - 1 - i) + (len2 - 1 - j)` to convert to place value positions.
|
||||
|
||||
The cleanest approach: iterate from right to left, then `i + j + 1` and `i + j` give the correct output positions.
|
||||
wrong_approach: "Using indices directly without accounting for place value"
|
||||
correct_approach: "Iterate right-to-left or adjust indices for place value"
|
||||
|
||||
- title: Forgetting to Handle Carries Properly
|
||||
description: |
|
||||
Each digit position can accumulate more than 9 during the multiplication phase. You must propagate carries correctly.
|
||||
|
||||
A common mistake is handling the carry only at the end, or forgetting that intermediate sums can exceed 9. Process the carry as you go by using `sum % 10` for the current position and `sum // 10` for the next position.
|
||||
wrong_approach: "Ignoring carries until the final conversion"
|
||||
correct_approach: "Handle carry during each digit multiplication"
|
||||
|
||||
- title: Leading Zeros in Result
|
||||
description: |
|
||||
The result array may have leading zeros (e.g., `099` for `9 × 11 = 99`). You must strip these before returning.
|
||||
|
||||
However, be careful not to strip all zeros if the result is `0` — ensure at least one zero remains for the `"0"` case.
|
||||
wrong_approach: "Returning result with leading zeros"
|
||||
correct_approach: "Strip leading zeros but preserve '0' for zero result"
|
||||
|
||||
key_takeaways:
|
||||
- "**Simulate manual algorithms**: When built-ins are forbidden, implement the algorithm you'd use by hand — here, grade-school long multiplication"
|
||||
- "**Position mapping**: The product of digits at positions `i` and `j` contributes to positions `i + j` and `i + j + 1` — this is the core insight"
|
||||
- "**Arbitrary precision**: This technique handles numbers far larger than native integer types can store"
|
||||
- "**Foundation for big integer libraries**: This is essentially how BigInteger multiplication works under the hood (with optimisations like Karatsuba for very large numbers)"
|
||||
|
||||
time_complexity: "O(m × n). We multiply each digit of `num1` (length m) with each digit of `num2` (length n)."
|
||||
space_complexity: "O(m + n). We use an array of size `m + n` to store the result digits."
|
||||
|
||||
solutions:
|
||||
- approach_name: Grade-School Multiplication
|
||||
is_optimal: true
|
||||
code: |
|
||||
def multiply(num1: str, num2: str) -> str:
|
||||
# Handle multiplication by zero
|
||||
if num1 == "0" or num2 == "0":
|
||||
return "0"
|
||||
|
||||
m, n = len(num1), len(num2)
|
||||
# Result can have at most m + n digits
|
||||
result = [0] * (m + n)
|
||||
|
||||
# Multiply each digit, right to left
|
||||
for i in range(m - 1, -1, -1):
|
||||
for j in range(n - 1, -1, -1):
|
||||
# Get the actual digit values
|
||||
digit1 = ord(num1[i]) - ord('0')
|
||||
digit2 = ord(num2[j]) - ord('0')
|
||||
|
||||
# Multiply and add to existing value at this position
|
||||
product = digit1 * digit2
|
||||
# Positions in result array
|
||||
p1, p2 = i + j, i + j + 1
|
||||
|
||||
# Add product to the lower position
|
||||
total = product + result[p2]
|
||||
|
||||
# Store digit and carry
|
||||
result[p2] = total % 10
|
||||
result[p1] += total // 10
|
||||
|
||||
# Convert to string, skipping leading zeros
|
||||
result_str = ''.join(map(str, result))
|
||||
return result_str.lstrip('0') or '0'
|
||||
explanation: |
|
||||
**Time Complexity:** O(m × n) — We perform m × n single-digit multiplications.
|
||||
|
||||
**Space Complexity:** O(m + n) — The result array stores the product digits.
|
||||
|
||||
This approach simulates the long multiplication method taught in schools. We multiply each digit pair and accumulate results at the appropriate positions, handling carries as we go. The key insight is that digits at indices `i` and `j` contribute to positions `i + j` and `i + j + 1` in the result.
|
||||
|
||||
- approach_name: With Separate Carry Pass
|
||||
is_optimal: false
|
||||
code: |
|
||||
def multiply(num1: str, num2: str) -> str:
|
||||
if num1 == "0" or num2 == "0":
|
||||
return "0"
|
||||
|
||||
m, n = len(num1), len(num2)
|
||||
# Accumulate products without handling carries yet
|
||||
result = [0] * (m + n)
|
||||
|
||||
# Reverse for easier position calculation
|
||||
num1 = num1[::-1]
|
||||
num2 = num2[::-1]
|
||||
|
||||
# Multiply all digit pairs
|
||||
for i in range(m):
|
||||
for j in range(n):
|
||||
digit1 = ord(num1[i]) - ord('0')
|
||||
digit2 = ord(num2[j]) - ord('0')
|
||||
# Position i + j gets this product
|
||||
result[i + j] += digit1 * digit2
|
||||
|
||||
# Now handle all carries in a separate pass
|
||||
carry = 0
|
||||
for i in range(len(result)):
|
||||
total = result[i] + carry
|
||||
result[i] = total % 10
|
||||
carry = total // 10
|
||||
|
||||
# Remove trailing zeros (they're leading after we reverse)
|
||||
while len(result) > 1 and result[-1] == 0:
|
||||
result.pop()
|
||||
|
||||
# Reverse back and convert to string
|
||||
return ''.join(map(str, result[::-1]))
|
||||
explanation: |
|
||||
**Time Complexity:** O(m × n) — Same asymptotic complexity as the optimal approach.
|
||||
|
||||
**Space Complexity:** O(m + n) — Same space usage.
|
||||
|
||||
This alternative reverses the strings first for simpler position indexing, accumulates all products without immediate carry handling, then propagates carries in a separate pass. While equally efficient, it's slightly less elegant than handling carries inline. Included to show a valid alternative approach.
|
||||
Reference in New Issue
Block a user