questions M-R
This commit is contained in:
169
backend/data/questions/product-of-array-except-self.yaml
Normal file
169
backend/data/questions/product-of-array-except-self.yaml
Normal file
@@ -0,0 +1,169 @@
|
||||
title: Product of Array Except Self
|
||||
slug: product-of-array-except-self
|
||||
difficulty: medium
|
||||
leetcode_id: 238
|
||||
leetcode_url: https://leetcode.com/problems/product-of-array-except-self/
|
||||
categories:
|
||||
- arrays
|
||||
patterns:
|
||||
- prefix-sum
|
||||
|
||||
description: |
|
||||
Given an integer array `nums`, return an array `answer` such that `answer[i]` is equal to the product of all the elements of `nums` except `nums[i]`.
|
||||
|
||||
The product of any prefix or suffix of `nums` is **guaranteed** to fit in a **32-bit** integer.
|
||||
|
||||
You must write an algorithm that runs in **O(n)** time and **without using the division operation**.
|
||||
|
||||
constraints: |
|
||||
- `2 <= nums.length <= 10^5`
|
||||
- `-30 <= nums[i] <= 30`
|
||||
- The product of any prefix or suffix of `nums` is guaranteed to fit in a 32-bit integer
|
||||
|
||||
examples:
|
||||
- input: "nums = [1,2,3,4]"
|
||||
output: "[24,12,8,6]"
|
||||
explanation: "answer[0] = 2×3×4 = 24, answer[1] = 1×3×4 = 12, answer[2] = 1×2×4 = 8, answer[3] = 1×2×3 = 6"
|
||||
- input: "nums = [-1,1,0,-3,3]"
|
||||
output: "[0,0,9,0,0]"
|
||||
explanation: "Any position except index 2 includes the zero, making the product 0. Position 2 excludes the zero: (-1)×1×(-3)×3 = 9"
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
For each position `i`, we need the product of everything **except** `nums[i]`. If division were allowed, we could compute the total product and divide by `nums[i]`. But division is forbidden (and would fail with zeros anyway).
|
||||
|
||||
Think of it differently: the product of "everything except position i" equals:
|
||||
- (product of everything **left** of i) × (product of everything **right** of i)
|
||||
|
||||
This is the **prefix-suffix** insight! For each position:
|
||||
- **Prefix product**: multiply all elements from the start up to (but not including) position i
|
||||
- **Suffix product**: multiply all elements from position i+1 to the end
|
||||
|
||||
If we can compute both efficiently, we just multiply them together.
|
||||
|
||||
The clever optimisation: we can use the output array itself to store prefix products in a left-to-right pass, then multiply in suffix products with a right-to-left pass using a single running variable.
|
||||
|
||||
approach: |
|
||||
We solve this using **Two-Pass Prefix/Suffix Products**:
|
||||
|
||||
**Step 1: Initialise the answer array**
|
||||
|
||||
- Create `answer` of length n, initialised to 1
|
||||
- This array will hold our final results
|
||||
|
||||
|
||||
|
||||
**Step 2: Left pass — compute prefix products**
|
||||
|
||||
- Track `left_product = 1` (product of elements to the left)
|
||||
- For each `i` from 0 to n-1:
|
||||
- Set `answer[i] = left_product` (product of all elements before i)
|
||||
- Update `left_product *= nums[i]` (include current element for next position)
|
||||
|
||||
|
||||
|
||||
**Step 3: Right pass — multiply by suffix products**
|
||||
|
||||
- Track `right_product = 1` (product of elements to the right)
|
||||
- For each `i` from n-1 down to 0:
|
||||
- Multiply `answer[i] *= right_product` (now contains prefix × suffix)
|
||||
- Update `right_product *= nums[i]` (include current element for next position)
|
||||
|
||||
|
||||
|
||||
**Step 4: Return the answer**
|
||||
|
||||
- Each `answer[i]` now contains the product of all elements except `nums[i]`
|
||||
|
||||
|
||||
|
||||
Example walkthrough with `[1,2,3,4]`:
|
||||
- After left pass: `[1, 1, 2, 6]` (prefix products)
|
||||
- After right pass: `[24, 12, 8, 6]` (prefix × suffix)
|
||||
|
||||
common_pitfalls:
|
||||
- title: Using Division
|
||||
description: |
|
||||
The problem explicitly forbids the division operator. Even if allowed, division fails when the array contains zeros (division by zero).
|
||||
|
||||
The prefix-suffix approach works without division and handles zeros naturally.
|
||||
wrong_approach: "total_product // nums[i]"
|
||||
correct_approach: "Prefix product × suffix product"
|
||||
|
||||
- title: Using Extra Space for Prefix and Suffix Arrays
|
||||
description: |
|
||||
A straightforward approach creates separate `prefix[]` and `suffix[]` arrays, using O(n) extra space.
|
||||
|
||||
The optimal solution reuses the output array for prefix products and uses a single variable for the running suffix product — O(1) extra space.
|
||||
wrong_approach: "prefix = []; suffix = [] — O(n) extra space"
|
||||
correct_approach: "Use output array for prefix, single variable for suffix"
|
||||
|
||||
- title: Off-by-One in Product Accumulation
|
||||
description: |
|
||||
The key insight: update `answer[i]` **before** multiplying `nums[i]` into the running product. This ensures `nums[i]` itself is excluded from the product at position i.
|
||||
|
||||
If you multiply first, you'll include `nums[i]` in its own product.
|
||||
wrong_approach: "left_product *= nums[i]; answer[i] = left_product"
|
||||
correct_approach: "answer[i] = left_product; left_product *= nums[i]"
|
||||
|
||||
key_takeaways:
|
||||
- "**Prefix/suffix products**: A powerful pattern for 'exclude current element' problems"
|
||||
- "**Two-pass technique**: Build partial results in one direction, complete them in the other"
|
||||
- "**Space optimisation**: The output array can store intermediate results, achieving O(1) extra space"
|
||||
- "**No division needed**: This approach naturally handles zeros without special cases"
|
||||
|
||||
time_complexity: "O(n). Two passes through the array, each doing O(1) work per element."
|
||||
space_complexity: "O(1). Only two variables (`left_product` and `right_product`) beyond the output array. The output array doesn't count as extra space per the problem statement."
|
||||
|
||||
solutions:
|
||||
- approach_name: Two-Pass with O(1) Space
|
||||
is_optimal: true
|
||||
code: |
|
||||
def product_except_self(nums: list[int]) -> list[int]:
|
||||
n = len(nums)
|
||||
answer = [1] * n
|
||||
|
||||
# Left pass: answer[i] = product of all elements to the LEFT of i
|
||||
left_product = 1
|
||||
for i in range(n):
|
||||
answer[i] = left_product
|
||||
left_product *= nums[i] # Include nums[i] for positions after i
|
||||
|
||||
# Right pass: multiply by product of all elements to the RIGHT of i
|
||||
right_product = 1
|
||||
for i in range(n - 1, -1, -1):
|
||||
answer[i] *= right_product
|
||||
right_product *= nums[i] # Include nums[i] for positions before i
|
||||
|
||||
return answer
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — Two linear passes.
|
||||
|
||||
**Space Complexity:** O(1) — Only two extra variables (output array doesn't count).
|
||||
|
||||
First pass stores prefix products in the answer array. Second pass multiplies each position by the suffix product. The result is the product of all elements except the current one.
|
||||
|
||||
- approach_name: Separate Prefix/Suffix Arrays
|
||||
is_optimal: false
|
||||
code: |
|
||||
def product_except_self(nums: list[int]) -> list[int]:
|
||||
n = len(nums)
|
||||
|
||||
# Build prefix products: prefix[i] = product of nums[0..i-1]
|
||||
prefix = [1] * n
|
||||
for i in range(1, n):
|
||||
prefix[i] = prefix[i - 1] * nums[i - 1]
|
||||
|
||||
# Build suffix products: suffix[i] = product of nums[i+1..n-1]
|
||||
suffix = [1] * n
|
||||
for i in range(n - 2, -1, -1):
|
||||
suffix[i] = suffix[i + 1] * nums[i + 1]
|
||||
|
||||
# Combine: answer[i] = prefix[i] * suffix[i]
|
||||
return [prefix[i] * suffix[i] for i in range(n)]
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — Three linear passes.
|
||||
|
||||
**Space Complexity:** O(n) — Two extra arrays for prefix and suffix products.
|
||||
|
||||
This version is easier to understand. We compute prefix and suffix products separately, then combine them. The optimal version merges these steps to save space.
|
||||
Reference in New Issue
Block a user