Generated by Codex with GPT-5
Quick facts
- Difficulty:
MEDIUM - Problem: Product of Array Except Self
- Main tags:
Array,Prefix Sum
What the problem is really asking
For each index, the answer should be the product of every number except the one currently sitting at that index.
If the input is [1, 2, 3, 4], then:
- the answer at index
0is2 * 3 * 4 = 24 - the answer at index
1is1 * 3 * 4 = 12 - the answer at index
2is1 * 2 * 4 = 8 - the answer at index
3is1 * 2 * 3 = 6
The straightforward but slow way is to recompute that product from scratch for
every position. That takes O(n^2) time, which is too expensive.
The real challenge is to reuse work so each position can be answered in constant time after a small number of full passes through the array.
Why the obvious division trick is not the real answer
A tempting idea is:
- multiply every number together once
- divide by
nums[i]for each position
That would be simple, but the problem explicitly forbids division. More importantly, division behaves badly when zeros appear.
For example, in [1, 2, 0, 4], the total product is 0, which does not tell
the full story. The correct answer is [0, 0, 8, 0], because only the zero’s
position gets the product of the nonzero values. A good solution should handle
zeros naturally without special-case arithmetic tricks.
The key idea: split the work into left and right products
For any index i, the desired answer can be written as:
- product of everything to the left of
i - multiplied by
- product of everything to the right of
i
So instead of asking, “How do I multiply everything except this one element in one shot?”, the better question is:
“Can I know the left-side product and the right-side product separately?”
Yes.
If one pass stores the product of all numbers before each index, and another pass provides the product of all numbers after each index, then multiplying those two pieces gives the answer for that index.
That is the whole insight.
How to derive the optimal solution
The easiest correct version uses two helper arrays:
prefix[i]= product of all elements before indexisuffix[i]= product of all elements after indexi
Then answer[i] = prefix[i] * suffix[i].
That already gives O(n) time, which is the big win. But it uses extra space
for both helper arrays.
The final optimization is noticing that one of those arrays does not need to be stored separately.
First, build the answer array so that answer[i] contains the prefix product
for index i.
Then sweep from right to left with a single running variable called something
like suffix_product. At each step:
- multiply
answer[i]by the current suffix product - update the suffix product by multiplying in
nums[i]
That way, the answer array temporarily holds the left-side products and then gets updated in place with the right-side products.
The result is:
O(n)timeO(1)extra space if the output array does not count against space usage
That last caveat is exactly how LeetCode defines the problem.
Python solution
from __future__ import annotations
from typing import List, Sequence
class ProductExceptSelfCalculator:
"""Compute the product of all values except the current index."""
def compute(self, values: Sequence[int]) -> List[int]:
"""
Return an array where each position contains the product of all
elements in ``values`` except the element at that position.
Time: O(n)
Extra space: O(1), excluding the returned output array
"""
self._validate_input(values)
result = self._build_prefix_products(values)
self._apply_suffix_products(values, result)
return result
def _build_prefix_products(self, values: Sequence[int]) -> List[int]:
"""Store each index's left-side product directly in the output array."""
result = [1] * len(values)
prefix_product = 1
for index, value in enumerate(values):
result[index] = prefix_product
prefix_product *= value
return result
def _apply_suffix_products(
self,
values: Sequence[int],
result: List[int],
) -> None:
"""Multiply each stored prefix product by the matching right-side product."""
suffix_product = 1
for index in range(len(values) - 1, -1, -1):
result[index] *= suffix_product
suffix_product *= values[index]
def _validate_input(self, values: Sequence[int]) -> None:
if len(values) < 2:
raise ValueError("values must contain at least two integers.")
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
"""LeetCode entry point."""
calculator = ProductExceptSelfCalculator()
return calculator.compute(nums)Why this works
During the left-to-right pass, result[i] becomes the product of everything
before index i.
During the right-to-left pass, suffix_product represents the product of
everything after index i.
So at the moment the algorithm updates result[i], it is multiplying:
- left-side product for
i - by
- right-side product for
i
That is exactly the quantity the problem asks for.
The solution also handles zeros correctly without any special branching:
- if there is one zero, every position except the zero’s position ends up with a zero factor somewhere in its left or right product
- if there are multiple zeros, every answer becomes zero
- if there are no zeros, the normal prefix/suffix logic still works
Because each pass only touches every element once, the total runtime is
O(n). Because it keeps only a few running variables besides the output array,
the extra space stays O(1).
Interview follow-ups
What if the interviewer allows division and asks for the simplest solution?
If division is explicitly allowed, the simplest idea is to compute the total product and divide by each element. That works cleanly only when there are no zeros. As soon as zeros appear, the logic splits into cases: zero zeros, one zero, or multiple zeros. With one zero, only the zero’s position gets the product of the nonzero values; with multiple zeros, every answer is zero.
That approach can still run in O(n) time and O(1) extra space, but it is
less robust and less elegant than the prefix/suffix method because the special
cases are entirely about division, not about the actual structure of the
problem. For the original LeetCode version, the prefix/suffix approach is the
better interview answer because it works uniformly and does not depend on
fragile arithmetic casework.
How would the solution change if the interviewer wanted a streaming version?
If numbers arrive one at a time and the goal is to produce the full final answer only after the stream ends, the same core idea still applies: some form of left-context and right-context is needed for each position. The difficulty is that a single left-to-right stream knows prefixes immediately but does not know future suffixes yet.
One practical answer is to store the numbers, build prefix products during
ingestion, and then do a reverse pass once the stream is complete. That still
gives O(n) time overall, but it requires holding the input so the reverse pass
can recover suffix products. If the interviewer asks for online answers after
each insertion, the problem becomes fundamentally different, because adding a
new element changes the answer for every earlier index. That shifts the
discussion toward dynamic data structures and update costs rather than the
static one-shot algorithm used in the original problem.
What if the array is so large that multiplication may overflow fixed-width integers?
In Python, integer overflow is not a concern because integers grow arbitrarily large. In languages with fixed-width integer types, though, the interviewer may want to know whether the computed products are guaranteed to fit within the allowed range.
The algorithmic structure does not change: prefix and suffix products are still
the right idea. The real issue is numeric representation. If overflow is
possible, the implementation may need a wider type such as long long, or the
problem must define modular arithmetic, saturation behavior, or explicit bounds
that make overflow impossible. This is a good example of separating algorithmic
correctness from representation correctness. The prefix/suffix method remains
optimal for the problem shape, while the exact integer type is an engineering
detail that depends on the language and constraints.
Could this be adapted to operators other than multiplication?
Sometimes an interviewer generalizes the problem to ask whether the same idea
works for another associative operation. The important property here is that the
answer at index i can be decomposed into “combine everything to the left” and
“combine everything to the right,” then merge those two summaries.
That works well for many associative operations such as addition, minimum, or maximum, although the interpretation changes. For example, “sum except self” can be handled with the same prefix/suffix decomposition, though there is an even simpler total-sum approach. The tradeoff is that not every operation has a useful identity value, clean inverse, or meaningful notion of combining left and right summaries. In interview terms, the prefix/suffix pattern is broader than this one problem, but it applies best when the operation composes cleanly over disjoint segments.