Given an array of integers, return an array res so that res[i] is equal to the product of all the elements of the input array except nums[i] itself.
Input: nums = [2, 3, 1, 4, 5]
Output: [60, 40, 120, 30, 24]
Explanation: The output value at index 0 is the product of all numbers except nums[0] (3⋅1⋅4⋅5 = 60). The same logic applies to the rest of the output.
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
The lazy way: multiply everything together once, then for each slot divide out that slot’s value. It works… until a single 0 appears — now you are dividing by zero, and the grand product is 0 anyway so you have lost all information. The fix is to build each answer from two running products: everything to its left times everything to its right. No division, and zeros are handled for free.
The straightforward solution to this problem is to find the total product of the array and divide it by each of the values in nums individually to get the output array:
This approach allows us to solve the problem in linear time and constant space. However, a potential follow-up question by an interviewer is: what if we can’t use division? Let’s explore a solution to this.
Avoiding division
A brute force approach involves calculating the output value for each index one by one. This would take time per index, leading to an overall time complexity of , where denotes the length of the array. This is inefficient, so let's look at other approaches.
An important insight is that the output for any given index can be determined by multiplying two things:
Why is this helpful? If we have precomputed the products of all values to the left and right of each index, we can quickly calculate the output for each index. More specifically, we would need two arrays that contain the left and right products of each index, respectively:
left_products: an array where left_products[i] is the product of all values to the left of i.
right_products: an array where right_products[i] is the product of all values to the right of i.
To obtain the left_products array, we need to keep track of a cumulative product of all elements we encounter as we move from left to right. The value of this product at a specific index should represent the product of all values to its left. The same is true of the right_products array, but the cumulative products start from the right. Once we have these arrays, multiplying the left and right product values at each index gives us the output value of that index.
Since the left and right product arrays are formed through cumulative multiplication, this leads us to the concept of prefix products.
Prefix products
Prefix products are created in the same way as a prefix sum array, with two key differences:
Let’s try creating the left_products array, initializing it with 1 at index 0:
For each subsequent index in the left_products array, we calculate its value by multiplying the running product by the previous value in the nums array:
The same can be done for the right_products array, but starting on the right and moving leftward:
Once both arrays are populated, we can compute each value of the output array, where res[i] is equal to the product of left_products[i] and right_products[i], as previously demonstrated.
Reducing space
We have successfully found a solution that doesn't involve division and runs in linear time. However, this solution takes up linear space due to the left and right product arrays. Can we compute the output array in place without taking up extra space?
An important thing to realize is that we don’t necessarily need to create the left and right product arrays to populate the output array. Instead, we can directly compute and store the left and right products in the output array as we calculate them.
This can be done in two steps:
1. First, populate the output array (res) the same way we populated left_products. This prepares the output array to be multiplied by the right products:
2. Then, instead of populating a right_products array, we directly multiply the running product from the right (right_product) into the output array:
from typing import List
def product_array_without_current_element(nums: List[int]) -> List[int]:
n = len(nums)
res = [1] * n
# Populate the output with the running left product.
for i in range(1, n):
res[i] = res[i - 1] * nums[i - 1]
# Multiply the output with the running right product, from right to left.
right_product = 1
for i in range(n - 1, -1, -1):
res[i] *= right_product
right_product *= nums[i]
return resvector<long long> productArrayWithoutCurrentElement(vector<int>& nums) {
int n = nums.size();
vector<long long> res(n, 1);
long long prefix = 1;
for (int i = 0; i < n; i++) {
res[i] = prefix; // product of everything to the LEFT of i
prefix *= nums[i];
}
long long suffix = 1;
for (int i = n - 1; i >= 0; i--) {
res[i] *= suffix; // fold in product of everything to the RIGHT
suffix *= nums[i];
}
return res;
}long[] productArrayWithoutCurrentElement(int[] nums) {
int n = nums.length;
long[] res = new long[n];
long prefix = 1;
for (int i = 0; i < n; i++) { res[i] = prefix; prefix *= nums[i]; }
long suffix = 1;
for (int i = n - 1; i >= 0; i--) { res[i] *= suffix; suffix *= nums[i]; }
return res;
}Time complexity: The time complexity of product_array_without_current_element is because we iterate over the nums array twice.
Space complexity: The space complexity is . The res array is not included in the space complexity analysis.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
[1, 2, 3, 4]Pass 1 (left → right) — write the running left-product into res[i], then multiply nums[i] into prefix:
| i | res[i] = prefix | prefix ×= nums[i] |
|---|---|---|
| 0 | res[0] = 1 | 1 × 1 = 1 |
| 1 | res[1] = 1 | 1 × 2 = 2 |
| 2 | res[2] = 2 | 2 × 3 = 6 |
| 3 | res[3] = 6 | 6 × 4 = 24 |
Pass 2 (right → left) — multiply res[i] by the running right-product suffix, then fold nums[i] into suffix:
| i | res[i] ×= suffix | suffix ×= nums[i] |
|---|---|---|
| 3 | 6 × 1 = 6 | 1 × 4 = 4 |
| 2 | 2 × 4 = 8 | 4 × 3 = 12 |
| 1 | 1 × 12 = 12 | 12 × 2 = 24 |
| 0 | 1 × 24 = 24 | 24 × 1 = 24 |
[-1, 1, 0, -3, 3]There is exactly one 0 (at index 2). Intuitively, only that slot can end up non-zero — every other slot’s “others” still include the zero. Watch the two passes make that happen automatically.
| i | nums[i] | left product (after pass 1) | right product | res[i] = left × right |
|---|---|---|---|---|
| 0 | −1 | 1 | 1·0·(−3)·3 = 0 | 1 × 0 = 0 |
| 1 | 1 | −1 | 0·(−3)·3 = 0 | −1 × 0 = 0 |
| 2 | 0 | −1·1 = −1 | (−3)·3 = −9 | −1 × −9 = 9 |
| 3 | −3 | −1·1·0 = 0 | 3 | 0 × 3 = 0 |
| 4 | 3 | −1·1·0·(−3) = 0 | 1 | 0 × 1 = 0 |
Take-away: the single zero “poisons” the left/right product for every other index, but at the zero’s own index neither running product has multiplied it in yet — so only res[2] survives. Division could never do this cleanly.
res = [1] * n starts everything at 1. The first loop writes left products; the reversed range(n-1, -1, -1) loop folds in right products.
vector<long long> res(n, 1) initializes to 1 and uses 64-bit so large products don’t overflow. Same two loops; the second counts down with i--.
res[i] = prefix overwrites each slot in pass 1 (no need to pre-fill). Pass 2 walks backward with i--. Identical arithmetic to the other two.