238. Product of Array Except Self
Problem Description
You are given an integer array nums
. Your task is to create a new array answer
where each element answer[i]
contains the product of all elements in nums
except for nums[i]
itself.
For example, if nums = [1, 2, 3, 4]
, then answer = [24, 12, 8, 6]
because:
answer[0] = 2 × 3 × 4 = 24
(product of all exceptnums[0]
)answer[1] = 1 × 3 × 4 = 12
(product of all exceptnums[1]
)answer[2] = 1 × 2 × 4 = 8
(product of all exceptnums[2]
)answer[3] = 1 × 2 × 3 = 6
(product of all exceptnums[3]
)
The problem has the following constraints:
- The algorithm must run in
O(n)
time complexity - You cannot use the division operation
- The product of any prefix or suffix of the array is guaranteed to fit in a 32-bit integer
The solution uses a two-pass approach. In the first pass from left to right, it calculates the product of all elements to the left of each position. In the second pass from right to left, it multiplies each position by the product of all elements to its right. This way, each position ends up with the product of all elements except itself, achieving the required result without using division and in linear time.
Intuition
Since we can't use division, we need to think about what the product of all elements except nums[i]
actually represents. For any position i
, this product can be broken down into two parts:
- The product of all elements to the left of
i
- The product of all elements to the right of
i
If we multiply these two parts together, we get exactly what we want - the product of all elements except nums[i]
.
Let's visualize this with an example. For nums = [1, 2, 3, 4]
and position i = 2
(value 3):
- Elements to the left:
[1, 2]
, product =1 × 2 = 2
- Elements to the right:
[4]
, product =4
- Final answer for position 2:
2 × 4 = 8
This observation leads us to a two-pass strategy:
First Pass (Left to Right): We accumulate the product of all elements to the left of each position. For position i
, we store the product of nums[0] × nums[1] × ... × nums[i-1]
. We use a running product variable left
that starts at 1 and gets multiplied by each element as we go.
Second Pass (Right to Left): We accumulate the product of all elements to the right and multiply it with what we already have. For position i
, we multiply the stored left product by the product of nums[i+1] × nums[i+2] × ... × nums[n-1]
. We use another running product variable right
that accumulates from the right side.
The beauty of this approach is that we build the answer incrementally without ever needing to calculate the full product and divide. Each position gets exactly the product it needs through these two complementary passes.
Solution Approach
We implement the two-pass approach using two variables left
and right
to track the running products, and an answer array ans
to store the results.
Initialization:
- Create an answer array
ans
of sizen
filled with zeros - Initialize
left = 1
andright = 1
to represent empty products
First Pass (Left to Right):
for i, x in enumerate(nums):
ans[i] = left
left *= x
In this pass, for each position i
:
- Store the current
left
product inans[i]
- this represents the product of all elements before positioni
- Update
left
by multiplying it with the current elementnums[i]
After this pass, ans[i]
contains the product of all elements to the left of position i
.
Second Pass (Right to Left):
for i in range(n - 1, -1, -1):
ans[i] *= right
right *= nums[i]
In this reverse pass, for each position i
:
- Multiply
ans[i]
by the currentright
product - this combines the left product (already inans[i]
) with the product of all elements to the right - Update
right
by multiplying it with the current elementnums[i]
Example Walkthrough:
For nums = [1, 2, 3, 4]
:
After first pass:
ans = [1, 1, 2, 6]
(products of elements to the left)left
progresses as: 1 → 1 → 2 → 6 → 24
After second pass:
ans = [24, 12, 8, 6]
(final answer)right
progresses as: 1 → 4 → 12 → 24
The algorithm runs in O(n)
time with two linear passes and uses O(1)
extra space (excluding the output array), satisfying all the problem constraints without using division.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [2, 3, 4, 5]
.
Initial Setup:
- Create
ans = [0, 0, 0, 0]
- Set
left = 1
andright = 1
First Pass (Left to Right):
Step | Index | Current Element | Action | left before | left after | ans array |
---|---|---|---|---|---|---|
1 | 0 | 2 | ans[0] = left = 1 | 1 | 1 × 2 = 2 | [1, 0, 0, 0] |
2 | 1 | 3 | ans[1] = left = 2 | 2 | 2 × 3 = 6 | [1, 2, 0, 0] |
3 | 2 | 4 | ans[2] = left = 6 | 6 | 6 × 4 = 24 | [1, 2, 6, 0] |
4 | 3 | 5 | ans[3] = left = 24 | 24 | 24 × 5 = 120 | [1, 2, 6, 24] |
After the first pass, each position contains the product of all elements to its left.
Second Pass (Right to Left):
Step | Index | Current Element | Action | right before | right after | ans array |
---|---|---|---|---|---|---|
1 | 3 | 5 | ans[3] *= right = 24 × 1 | 1 | 1 × 5 = 5 | [1, 2, 6, 24] |
2 | 2 | 4 | ans[2] *= right = 6 × 5 | 5 | 5 × 4 = 20 | [1, 2, 30, 24] |
3 | 1 | 3 | ans[1] *= right = 2 × 20 | 20 | 20 × 3 = 60 | [1, 40, 30, 24] |
4 | 0 | 2 | ans[0] *= right = 1 × 60 | 60 | 60 × 2 = 120 | [60, 40, 30, 24] |
Final Result: ans = [60, 40, 30, 24]
Verification:
ans[0] = 60 = 3 × 4 × 5
(all except 2)ans[1] = 40 = 2 × 4 × 5
(all except 3)ans[2] = 30 = 2 × 3 × 5
(all except 4)ans[3] = 24 = 2 × 3 × 4
(all except 5)
Each position now contains the product of all elements except itself, achieved through combining left and right products without division.
Solution Implementation
1class Solution:
2 def productExceptSelf(self, nums: List[int]) -> List[int]:
3 """
4 Calculate the product of all elements except self for each position.
5 Uses two passes to avoid division and achieve O(n) time complexity.
6
7 Args:
8 nums: List of integers
9
10 Returns:
11 List where each element is the product of all other elements
12 """
13 n = len(nums)
14
15 # Initialize result array with zeros
16 result = [0] * n
17
18 # First pass: calculate products of all elements to the left
19 left_product = 1
20 for i in range(n):
21 # Store the product of all elements before current index
22 result[i] = left_product
23 # Update left_product for next iteration
24 left_product *= nums[i]
25
26 # Second pass: multiply by products of all elements to the right
27 right_product = 1
28 for i in range(n - 1, -1, -1):
29 # Multiply existing left product by right product
30 result[i] *= right_product
31 # Update right_product for next iteration
32 right_product *= nums[i]
33
34 return result
35
1class Solution {
2 public int[] productExceptSelf(int[] nums) {
3 int n = nums.length;
4 int[] result = new int[n];
5
6 // First pass: Calculate products of all elements to the left of each index
7 // For each position i, store the product of all elements before index i
8 int leftProduct = 1;
9 for (int i = 0; i < n; i++) {
10 result[i] = leftProduct; // Store product of all elements to the left
11 leftProduct *= nums[i]; // Update left product for next iteration
12 }
13
14 // Second pass: Calculate products of all elements to the right of each index
15 // Multiply each position by the product of all elements after index i
16 int rightProduct = 1;
17 for (int i = n - 1; i >= 0; i--) {
18 result[i] *= rightProduct; // Multiply by product of all elements to the right
19 rightProduct *= nums[i]; // Update right product for next iteration
20 }
21
22 return result;
23 }
24}
25
1class Solution {
2public:
3 vector<int> productExceptSelf(vector<int>& nums) {
4 int n = nums.size();
5 vector<int> result(n);
6
7 // First pass: Calculate prefix products (products of all elements to the left)
8 // For each position i, store the product of all elements before index i
9 int leftProduct = 1;
10 for (int i = 0; i < n; i++) {
11 result[i] = leftProduct; // Store product of all elements to the left of i
12 leftProduct *= nums[i]; // Update left product for next iteration
13 }
14
15 // Second pass: Calculate suffix products (products of all elements to the right)
16 // and multiply with the prefix products already stored in result
17 int rightProduct = 1;
18 for (int i = n - 1; i >= 0; i--) {
19 result[i] *= rightProduct; // Multiply with product of all elements to the right of i
20 rightProduct *= nums[i]; // Update right product for next iteration
21 }
22
23 return result;
24 }
25};
26
1/**
2 * Calculate the product of all elements in the array except self
3 * @param nums - Input array of numbers
4 * @returns Array where each element is the product of all elements except itself
5 */
6function productExceptSelf(nums: number[]): number[] {
7 const length: number = nums.length;
8 const result: number[] = new Array<number>(length);
9
10 // First pass: Calculate left products
11 // For each position, store the product of all elements to its left
12 let leftProduct: number = 1;
13 for (let i: number = 0; i < length; i++) {
14 result[i] = leftProduct;
15 leftProduct *= nums[i];
16 }
17
18 // Second pass: Calculate right products and combine with left products
19 // For each position, multiply the stored left product by the product of all elements to its right
20 let rightProduct: number = 1;
21 for (let i: number = length - 1; i >= 0; i--) {
22 result[i] *= rightProduct;
23 rightProduct *= nums[i];
24 }
25
26 return result;
27}
28
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the array nums
. The algorithm performs two separate passes through the array - the first loop iterates through all n
elements to calculate left products, and the second loop iterates through all n
elements again to calculate right products and combine them with left products. Since both loops run sequentially, the total time complexity is O(n) + O(n) = O(2n) = O(n)
.
Space Complexity: O(1)
(excluding the output array). The algorithm only uses two extra variables left
and right
to keep track of the running products, which requires constant space. The answer array ans
of size n
is required for the output and is not counted toward the space complexity as per the problem's convention. If we were to include the output array, the total space complexity would be O(n)
.
Common Pitfalls
1. Misunderstanding the Order of Operations in Each Pass
A frequent mistake is incorrectly ordering the assignment and multiplication operations within each loop. Developers often write:
# INCORRECT - First Pass
for i in range(n):
left_product *= nums[i] # Multiplying BEFORE storing
result[i] = left_product # This includes nums[i] in the product!
This error includes nums[i]
in its own product, violating the problem requirement. The correct approach stores the product BEFORE multiplying:
# CORRECT - First Pass
for i in range(n):
result[i] = left_product # Store current product first
left_product *= nums[i] # Then update for next iteration
2. Attempting to Optimize by Combining Both Passes
Some developers try to calculate both left and right products in a single loop:
# INCORRECT - Trying to do both passes simultaneously
for i in range(n):
result[i] = left_product
result[n-1-i] *= right_product # This overwrites left products!
left_product *= nums[i]
right_product *= nums[n-1-i]
This approach fails because the right-to-left calculations overwrite the left products before they can be properly combined. The two-pass approach is necessary to ensure each position receives both its left and right products.
3. Handling Edge Cases with Empty or Single-Element Arrays
While the provided solution handles these cases correctly, developers sometimes add unnecessary special case handling:
# UNNECESSARY complexity if n == 0: return [] if n == 1: return [1] # INCORRECT - should return [1] for input [x]
The two-pass algorithm naturally handles these cases:
- For
nums = []
: Returns[]
- For
nums = [5]
: Returns[1]
(product of zero elements is 1)
4. Using Division as a "Clever" Shortcut
When not carefully reading constraints, developers might attempt:
# VIOLATES CONSTRAINT - Uses division
total_product = 1
for num in nums:
total_product *= num
for i in range(n):
result[i] = total_product // nums[i] # Division not allowed!
This also fails when any element is zero, causing division by zero errors.
Solution to Avoid These Pitfalls:
-
Always store before updating: In each pass, assign the current accumulated product to the result array BEFORE multiplying by the current element.
-
Maintain two separate passes: Don't try to combine them - the algorithm's elegance comes from the clean separation of left and right product calculations.
-
Trust the algorithm: The two-pass approach naturally handles edge cases without special logic.
-
Follow constraints strictly: Even if division seems simpler, the constraint exists for good reasons (handling zeros, demonstrating understanding of the two-pointer technique).
What data structure does Breadth-first search typically uses to store intermediate states?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!