Files
codetutor/backend/data/questions/product-of-array-except-self.yaml

194 lines
8.0 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
function_signature: "def product_except_self(nums: list[int]) -> list[int]:"
test_cases:
visible:
- input: { nums: [1, 2, 3, 4] }
expected: [24, 12, 8, 6]
- input: { nums: [-1, 1, 0, -3, 3] }
expected: [0, 0, 9, 0, 0]
hidden:
- input: { nums: [2, 3] }
expected: [3, 2]
- input: { nums: [1, 1, 1, 1] }
expected: [1, 1, 1, 1]
- input: { nums: [0, 0] }
expected: [0, 0]
- input: { nums: [5, -2, 4] }
expected: [-8, 20, -10]
- input: { nums: [1, 2, 3, 4, 5] }
expected: [120, 60, 40, 30, 24]
- input: { nums: [0, 1, 2, 3] }
expected: [6, 0, 0, 0]
- input: { nums: [-1, -1, -1] }
expected: [1, 1, 1]
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
&nbsp;
**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)
&nbsp;
**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)
&nbsp;
**Step 4: Return the answer**
- Each `answer[i]` now contains the product of all elements except `nums[i]`
&nbsp;
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.