DSA sheet · Arrays · Prefix sum pattern

Product of Array Except Self

This is the third problem of the prefix sum pattern. It is not a classic "add things up" prefix sum. Here we build running products: one coming from the left, one coming from the right. The teacher first writes the slow, obvious solution (for every index, multiply everything else), shows why it is too slow for n = 10⁵, goes through the four array patterns to pick the right one, and then reuses already-computed products so the whole job takes just two simple loops. She also says this question is asked in interviews again and again, so it is worth learning well.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

What is a prefix sum?

A prefix of an array is a piece that starts at index 0 and stops somewhere: nums[0..i]. The prefix sum at index i is the total of that piece: prefix[i] = nums[0] + nums[1] + … + nums[i].

We never add the whole piece again from scratch. Each cell is built from the cell just before it: prefix[i] = prefix[i-1] + nums[i]. "Everything up to i−1" is already saved, so we only add one new number.

index0123
nums2345
prefix259142 · 2+3 · 5+4 · 9+5

Why sum(l..r) = prefix[r] − prefix[l−1]

prefix[r] is the total of indexes 0…r. prefix[l−1] is the total of 0…l−1. Subtract, and the shared part 0…l−1 cancels. What is left is exactly l…r. Example: sum(1..3) = 3 + 4 + 5 = 12 = prefix[3] − prefix[0] = 14 − 2 ✓.

The "−1" trick: when l = 0 there is no prefix[-1]. We treat the empty prefix (nothing taken yet) as 0, because adding nothing gives 0. Many solutions store this as an extra cell at the start, or as {0: -1} in a hashmap.

The same idea with products: prefix product and suffix product

Replace "+" with "×" and you get a prefix product: the product of everything up to some index. A suffix is a piece that ends at the last index (nums[i..n−1]), so a suffix product is the product of everything from some index to the end.

For "empty" we now use 1, not 0, because multiplying by 1 changes nothing (just as adding 0 changes nothing). If nothing is on the left of index 0, its left product is 1.

Key fact for this problemThe product of everything except nums[i] = (product of everything left of i) × (product of everything right of i). Both parts are running products we can build one cell at a time.

The four array patterns (the teacher's checklist for optimising)

patternwhen it fits
Two pointerswe only care about the two numbers the pointers sit on, not what's between them (usually a sorted array).
Sliding windowwe need something (sum, product…) over a continuous block, and we know when to grow or shrink it.
Prefix sumwe compute running totals (or products) once and reuse them to answer many questions.
Kadane's algorithmwe need the maximum subarray sum when negative numbers are present.

Part A · Brute force: multiply everything except me

LeetCode 238

1The question in simple words

You get an integer array nums. Build a new array answer of the same length where answer[i] = the product of all the other numbers, i.e. everything except nums[i] itself.

index0123
nums1234
answer24128624 = 2·3·4 · 12 = 1·3·4 · 8 = 1·2·4 · 6 = 1·2·3

For index 0 we skip the 1 and multiply 2 × 3 × 4 = 24. For index 1 we skip the 2 and multiply the left side (1) with the right side (3 × 4): 12.

The catch: you are not allowed to use division.

Doubt: why does the question ban division? What would the division trick be?
→ The tempting shortcut: multiply the whole array once (1·2·3·4 = 24), then for each index answer = total ÷ nums[i]. That is exactly what the question forbids, so you must find another way. (It also breaks with zeros: if nums[i] is 0 you'd divide by 0, and if the array has a 0 the total is 0 for everyone.)

2What the constraints tell us

3Intuition: how would you do it by hand?

Point at one index; call that finger left. Now run a second finger, right, across the whole array from start to end, multiplying every number it touches, but skip the moment right lands on the same index as left. When right reaches the end, the product is the answer for that index. Then move left forward by one and repeat.

Worked example: nums = [2, 3, 4, 5], answer for index 0

Left is on index 0 (the 2). Right starts at 0: same as left → skip. Right at 1: product = 3. Right at 2: 3 × 4 = 12. Right at 3: 12 × 5 = 60. Store 60 in answer[0].

4Building the logic from examples

Where should the right finger start?

Doubt: why not start right at left (or left + 1) to save time?
→ Because then right only sees what is on the right side of left. We'd miss every number on the left side. For index 2 in [2, 3, 4, 5] we need 2 × 3 (left side) and 5 (right side). So right must start at 0 and go to the end every time, and simply skip one spot.

How do we "skip" ourselves?

Inside the inner loop: if right == left, do nothing for this step (continue). Otherwise multiply product *= nums[right].

What should product start at, and what should answer start at?

Doubt: numbers can be negative. Is it safe to fill the result with 0 at the start?
→ Yes. Every cell will be overwritten with its real product, so the starting value never matters. The starting value only matters when you're keeping a running max or min and comparing against it. Here we just overwrite.

5Approach steps

  1. Make res of size n.
  2. For each left from 0 to n − 1: set product = 1.
  3. For each right from 0 to n − 1: if right == left, skip; else product *= nums[right].
  4. After the inner loop, res[left] = product.
  5. Return res.

6Code (Python)

Brute force (correct, but TLE for n = 10⁵)
class Solution:
    def productExceptSelf(self, nums):
        n = len(nums)
        res = [0] * n                      # will be overwritten
        for left in range(n):              # the index we answer for
            product = 1
            for right in range(n):         # walk the WHOLE array
                if right == left:          # skip myself
                    continue
                product *= nums[right]
            res[left] = product
        return res

7Code line by line

linewhat it means
res = [0] * nThe answer array, same size as nums. 0 is a placeholder.
for left in range(n):Pick the index whose answer we're building.
product = 1Fresh product for this index. 1 is the "empty product".
for right in range(n):Visit every index, both sides of left.
if right == left: continueDon't multiply the number itself.
product *= nums[right]Multiply in every other number.
res[left] = productSave the finished product for this index.

8Dry run (hand table)

nums = [2, 3, 4, 5]. Each row is one value of left; "skip" marks where right == left.

leftright = 0right = 1right = 2right = 3res[left]
0skip1·3 = 33·4 = 1212·5 = 6060
11·2 = 2skip2·4 = 88·5 = 4040
21·2 = 22·3 = 6skip6·5 = 3030
31·2 = 22·3 = 66·4 = 24skip24

Answer [60, 40, 30, 24] ✓. Notice how often we redo the same work: "2 × 3" is computed again in rows 2 and 3. Part B removes exactly this waste.

9Complexity & remember

Remember the brute forceFor each index, a second pointer walks the whole array (from 0, not from left) and multiplies everything except the matching index. Product starts at 1. Correct but O(n²).

Part B · Optimal: left products, then right products

1The question

Same question, same "no division" rule. We want O(n).

2Constraints → which pattern?

n up to 10⁵ means we need about O(n). The teacher goes through the four patterns one by one:

3Intuition

Split the answer in two: answer[i] = (product of everything left of i) × (product of everything right of i).

nums:        2     3    [4]    5
             \_____/          \_/
           left part = 6   right part = 5      answer[2] = 6 × 5 = 30

Both parts are running products. Walk left → right once to get every left part. Walk right → left once to get every right part. Multiply them together. Two separate loops, no loop inside a loop.

4Building the logic from examples

Pass 1: left products, stored straight into res

Use nums = [2, 3, 4, 5].

Formula, pass 1res[i] = res[i-1] * nums[i-1], for i = 1 … n−1, with res[0] = 1.
"Everything left of me" = "everything left of my neighbour" × "my neighbour".
index0123
nums2345
res (left)126241 · 1×2 · 2×3 · 6×4
Doubt: is res the final answer now?
→ No. Each cell holds only the left half. For the 3 (index 1), res says 2, but the real answer is 2 × 4 × 5 = 40. We still need to multiply in the right side. (Only the last cell is already complete, because nothing is on its right.)

Pass 2: right products, from the back

Now walk from the last index to the first, keeping a running right_prod = "product of everything to the right of where I stand".

Doubt: in pass 1 we used res[i-1]. Why not use res[i+1] in pass 2, the same way?
→ Because after pass 2 touches a cell, it no longer holds a right-only product. Example: after index 3, res[3] = 24 = everything left of the 5. It is not "everything right of index 2" (which should be just 5). The cells are already full of left products, so they can't also store the right products. That's why the teacher keeps the right product in a separate variable, right_prod.
Doubt: why update right_prod after using it, not before?
→ At index i, right_prod must contain only numbers strictly to the right of i. If we multiplied nums[i] in first, we'd include the number itself, which is exactly what the question forbids. So: use it for res[i], then add nums[i] so it's ready for index i − 1.
Formula, pass 2res[i] = res[i] * right_prod, then right_prod *= nums[i], for i = n−1 down to 0, with right_prod = 1 at the start.

5Approach steps

  1. res = [1] * n (so res[0] = 1, the empty left product).
  2. For i from 1 to n − 1: res[i] = res[i-1] * nums[i-1].
  3. right_prod = 1.
  4. For i from n − 1 down to 0: res[i] *= right_prod, then right_prod *= nums[i].
  5. Return res.

6Code (Python)

Optimal: two passes, no division
class Solution:
    def productExceptSelf(self, nums):
        n = len(nums)
        res = [1] * n
        res[0] = 1                          # nothing on the left of index 0

        # pass 1: res[i] = product of everything LEFT of i
        for i in range(1, n):
            res[i] = res[i - 1] * nums[i - 1]

        # pass 2: multiply in the product of everything RIGHT of i
        right_prod = 1                      # nothing on the right of the last index
        for i in range(n - 1, -1, -1):
            res[i] = res[i] * right_prod
            right_prod *= nums[i]           # include nums[i] for the next index (i-1)

        return res

7Code line by line

linewhat it means
res = [1] * n res[0] = 1The output array. Index 0 has no left neighbours, so its left product is 1, no matter what nums[0] is.
for i in range(1, n):Start at 1, because index 0 is already done.
res[i] = res[i - 1] * nums[i - 1]Reuse the neighbour's left product and multiply in the neighbour. This is the "prefix" idea: no recomputing.
right_prod = 1Running product of everything to the right. Empty at the start → 1.
for i in range(n - 1, -1, -1):Walk from the last index back to 0 (Python's way of writing i--; the stop value −1 is not included).
res[i] = res[i] * right_prodLeft part (already in res[i]) × right part → final answer for i.
right_prod *= nums[i]Now that i is answered, add nums[i] to the right product for the next index on the left.
return resEvery cell now holds left × right.
Small slip in the videoWhile typing the Java code, the teacher first wrote the backwards loop with r++, then corrected it to r-- because we move from right to left. She also fixed a spelling mistake in the variable name before running. In Python, the backwards loop is range(n - 1, -1, -1).

8Dry run (hand table)

nums = [2, 3, 4, 5].

Pass 1 (left → right)

ires[i−1]nums[i−1]res[i] = res[i−1] × nums[i−1]res after this step
0——1 (nothing on the left)[1, 1, 1, 1]
1122[1, 2, 1, 1]
2236[1, 2, 6, 1]
36424[1, 2, 6, 24]

Pass 2 (right → left)

inums[i]right_prod usedres[i] = left × rightright_prod afterres after this step
35124 × 1 = 245[1, 2, 6, 24]
2456 × 5 = 3020[1, 2, 30, 24]
13202 × 20 = 4060[1, 40, 30, 24]
02601 × 60 = 60120[60, 40, 30, 24]
index0123
nums2345
left prod12624pass 1
right prod602051right_prod at each i in pass 2
answer60403024left × right

Matches the brute force ✓.

What happens with zeros?

No special code is needed. nums = [1, 2, 0, 4]: left products [1, 1, 2, 0], right products [0, 0, 4, 1] → answer [0, 0, 8, 0]. Only the zero's own index gets a non-zero answer (1 × 2 × 4). With two zeros, e.g. [0, 2, 0], every index still has some zero among the others → [0, 0, 0]. The division trick would crash here; this method doesn't care.

9Complexity & remember

Remember Product Except Selfanswer[i] = left product × right product. Pass 1: res[i] = res[i-1] * nums[i-1], res[0] = 1. Pass 2 backwards: res[i] *= right_prod, then right_prod *= nums[i]. No division.

Part C · Revision page

Brute forceLeft × right products
ideafor every i, multiply all j ≠ ireuse running products from both ends
loopstwo nested loopstwo loops one after the other
left partrecomputed every timeres[i] = res[i-1] * nums[i-1]
right partrecomputed every timevariable right_prod, built from the back
timeO(n²) ≈ 10¹⁰ → TLEO(n) ✓
extra spaceO(1)O(1) (output not counted)
patternfits here?why
Two pointersnowe need all the numbers, not just two
Sliding windowno"all except i" is not one continuous block
Kadane'snoit finds a max sum, not products for each index
Prefix (product)yescompute once, reuse for the next index
If you remember only 5 lines 1. No division allowed, so split the answer: everything on the left × everything on the right.
2. The empty product is 1 (index 0 has left product 1; the last index has right product 1).
3. Pass 1: res[i] = res[i-1] * nums[i-1].
4. Pass 2 (backwards): res[i] *= right_prod, then right_prod *= nums[i].
5. O(n) time, O(1) extra space; zeros and negatives need no special code.
Mistakes to avoid ✗ using division (forbidden, and it breaks on zeros)
✗ starting the brute-force inner loop at left (you lose the left side)
✗ starting product / right_prod at 0 instead of 1
✗ using res[i+1] for the right side (those cells already hold left products)
✗ updating right_prod before using it (includes the number itself)
✗ going forwards in pass 2 (i++ instead of i--)
test it yourself (paste under either solution)
s = Solution()
print(s.productExceptSelf([1, 2, 3, 4]))       # [24, 12, 8, 6]
print(s.productExceptSelf([2, 3, 4, 5]))       # [60, 40, 30, 24]
print(s.productExceptSelf([-1, 1, 0, -3, 3]))  # [0, 0, 9, 0, 0]
print(s.productExceptSelf([0, 2, 0]))          # [0, 0, 0]
print(s.productExceptSelf([3, 4]))             # [4, 3]

Based on this video: Product of Array Except Self | Prefix Sum pattern