Prefix Sum
Do the adding once up front, so that any "sum of a range" question later becomes a single subtraction.
1. What is this pattern for?
A prefix sum array stores a running total. prefix[i] is the sum of the first i numbers. After building it once in O(n), the sum of any slice nums[l..r] comes from one subtraction in O(1):
# The one formula to remember (prefix has a leading 0):
# sum of nums[left..right], both ends included, = prefix[right + 1] - prefix[left]
def slice_sum(prefix, left, right):
# odometer reading "after right" minus odometer reading "before left"
return prefix[right + 1] - prefix[left]
It's for problems that keep asking about totals over contiguous ranges (subarrays, or sub-rectangles in a grid), especially when there are many such questions or you'd otherwise need two nested loops.
2. How it works (bisected)
- Pad with a zero. Make
prefixone longer thannumsand setprefix[0] = 0. That zero means "sum of nothing", and it removes every edge case at index 0. - Accumulate.
prefix[i+1] = prefix[i] + nums[i]. Each cell is the odometer reading after visiting elementi. - Subtract to answer. Range
[l, r]= reading afterrminus reading beforel=prefix[r+1] - prefix[l]. - Level up: prefix + hash map. To count subarrays with sum
k, walk once and ask: "Have I seen a running total ofcurrent - kbefore?" If so, the slice between that earlier point and now sums tok.
3. Seeing a problem: how to spot Prefix Sum
Trigger words
- "sum of subarray", "range sum", "between index i and j"
- "number of subarrays whose sum equals / is divisible by k"
- "equal number of 0s and 1s" (map 0 to −1, then look for sum 0)
- "many queries" on an array that doesn't change
- "sub-matrix sum", "region sum" in a grid
Shape of the input
- An array or grid of numbers, possibly negative. Negatives break Sliding Window but not Prefix Sum.
- The answer depends on a contiguous slice.
- The brute force is "for every l, for every r, add up". That's
O(n²)orO(n³).
4. When to use it, and when not to
| Use Prefix Sum when… | Prefer something else when… |
|---|---|
| There are many range-sum queries on a static array | The array changes between queries: use a Fenwick / Segment tree |
| You're counting subarrays with sum = k, and values can be negative | All values are positive and you want the longest/shortest window: use Sliding Window, which uses O(1) space |
| "Equal count" problems that become "sum = 0" after a mapping | You need a max/min over a range, not a sum: use a sparse table or Monotonic Stack |
| Sub-rectangle sums in a grid (2-D prefix) | The operation can't be undone by subtraction (max, gcd…) |
Cost: build O(n) time and O(n) space; each query O(1). The prefix + hash map version is O(n) time and O(n) space.
5. The Python template (memorize this)
There are two templates. Template A answers "what is the sum of this range?" many times. Template B counts how many slices add up to exactly k.
# A) Range sums: build once, then answer each query in O(1)
def build_prefix(nums):
# [0] * (len(nums) + 1) makes a list of zeros, one longer than nums.
# prefix[0] stays 0 and means "the sum of nothing".
prefix = [0] * (len(nums) + 1)
for i in range(len(nums)):
number = nums[i]
# total after element i = total before element i + the element itself
prefix[i + 1] = prefix[i] + number
return prefix
def range_sum(prefix, left, right):
# Sum of nums[left..right], both ends included.
# prefix[right + 1] = total of everything up to and including index right
# prefix[left] = total of everything BEFORE index left
# Subtracting the two leaves exactly the slice we want.
return prefix[right + 1] - prefix[left]
What the code is doing, line by line:
build_prefixmakes a list of zeros that is one longer thannums. Slot 0 stays 0: the odometer before we start driving.- The
forloop visits every indexi. It reads the number at that index intonumber. prefix[i + 1] = prefix[i] + number: the total after elementiis the total before it plus the element. Soprefix[i]always means "sum of the firstinumbers".range_sumdoes one subtraction: the total up to and includingright, minus the total of everything beforeleft. What is left over is exactlynums[left..right].
Dry run on the example nums = [3, 1, 4, 1, 5]:
| i | number | prefix[i] | prefix[i + 1] | prefix after this step |
|---|---|---|---|---|
| 0 | 3 | 0 | 3 | [0, 3, 0, 0, 0, 0] |
| 1 | 1 | 3 | 4 | [0, 3, 4, 0, 0, 0] |
| 2 | 4 | 4 | 8 | [0, 3, 4, 8, 0, 0] |
| 3 | 1 | 8 | 9 | [0, 3, 4, 8, 9, 0] |
| 4 | 5 | 9 | 14 | [0, 3, 4, 8, 9, 14] |
Query range_sum(prefix, 1, 3) = prefix[4] - prefix[1] = 9 - 3 = 6, which matches 1 + 4 + 1.
# B) Count subarrays whose sum is exactly k (works with negative numbers)
def count_subarrays(nums, k):
# seen maps "a running total" -> "how many times we have reached it so far"
seen = {}
# Before reading any number the running total is 0 (the empty prefix).
# Recording it lets a slice that starts at index 0 be counted.
seen[0] = 1
running = 0
count = 0
for i in range(len(nums)):
number = nums[i]
# add this number to the running total
running = running + number
# If an earlier running total equals running - k, the numbers between
# that earlier point and here add up to exactly k.
needed = running - k
if needed in seen:
count = count + seen[needed]
# Only now record the current running total, for later positions.
if running not in seen:
seen[running] = 0
seen[running] = seen[running] + 1
return count
What the code is doing, line by line:
seenis a dictionary. Its keys are running totals we have reached, its values are how many times we reached them.seen[0] = 1records the empty prefix (total 0 before any number). Without it, a slice that starts at index 0 would never be counted.- For each number,
running = running + numbermoves the odometer forward. needed = running - kis the earlier total we would need. If we reached itseen[needed]times before, there are that many slices ending here that sum tok, so we add that tocount.- Last, we record the current
runninginseen(create the key with 0 first if it is new, then add 1). Recording after counting means we never count an empty slice.
Dry run on the example nums = [1, 1, 1], k = 2:
| i | number | running | needed = running - k | added to count | count | seen after |
|---|---|---|---|---|---|---|
| 0 | 1 | 1 | -1 | 0 | 0 | {0: 1, 1: 1} |
| 1 | 1 | 2 | 0 | 1 | 1 | {0: 1, 1: 1, 2: 1} |
| 2 | 1 | 3 | 1 | 1 | 2 | {0: 1, 1: 1, 2: 1, 3: 1} |
Result: 2 (the slices [1, 1] at indices 0..1 and 1..2).
6. How to remember it forever
How this hook works: what “Odometer minus odometer” means
What the slogan means, phrase by phrase
- “Odometer”
- The running total.
prefix[i]is the odometer reading after driving past the firstinumbers. It only ever adds, like a car's distance meter. - “minus odometer”
- Any stretch is the difference of two readings: the reading at the end minus the reading at the start. You never re-add the numbers in between.
- “Pad with a 0”
- A new car leaves the factory reading 0.
prefix[0] = 0is that reading, so a stretch that starts at index 0 still has a "start reading" to subtract.
Why this picture fits the pattern: Prefix-sum problems keep asking "what is the total between here and there?" That is exactly the question an odometer answers with one subtraction, after you've done the driving once.
The story → the code
| In the story | In the code |
|---|---|
| One leg of the trip | nums[i] |
| Odometer reading after leg i | prefix[i + 1] |
| The factory reading | prefix[0] = 0 |
| Trip from leg l to leg r | prefix[r + 1] - prefix[l] |
| A logbook of readings you've passed | seen (running total → how many times) |
Watch it on numbers
nums = [3, 1, 4, 1, 5], so the readings are prefix = [0, 3, 4, 8, 9, 14]. What is nums[1..3]?
- Reading after leg 3 is
prefix[4]= 9. - Reading before leg 1 is
prefix[1]= 3. - Trip distance = 9 − 3 = 6, and indeed 1 + 4 + 1 = 6.
- The whole array:
prefix[5] - prefix[0]= 14 − 0 = 14. The padded 0 makes this work without a special case.
The trap it saves you from
The off-by-one in prefix[r + 1] - prefix[l]. Say it as "reading after r minus reading before l" and the + 1 writes itself. Forgetting the 0 pad breaks every slice that starts at index 0.
It still works when…
Counting subarrays that sum to k (LeetCode 560): at each reading running, ask "did I pass the reading running - k earlier?" If the odometer said 1 and now says 3, that stretch was 2 km. On [1, 1, 1] with k = 2 the readings are 1, 2, 3: at 2 we passed 0 once, at 3 we passed 1 once, so the answer is 2.
Picture
A staircase where each step's height is the running total. Any slice's sum is the height difference between two steps.
Chant
"Pad zero, run total, right plus one minus left."
Hash-map twist
"What did I need to have seen?" → running − k. Store totals, not indices, when you're counting.
7. LeetCode problems, solved step by step
Medium 560. Subarray Sum Equals K Start here Google Meta Amazon Cisco Microsoft Apple
Problem: Given nums (can contain negatives) and k, return how many contiguous subarrays sum to k.
nums = [1,1,1], k = 22nums = [1,2,3], k = 32Constraints: 1 ≤ nums.length ≤ 2 * 10⁴ · -1000 ≤ nums[i] ≤ 1000 · -10⁷ ≤ k ≤ 10⁷
Hint 1
The sum of nums[i..j] equals prefix[j + 1] − prefix[i]. If that difference must be k, what earlier prefix are you looking for?
Hint 2
Walk once, keeping a running sum. Count how many earlier prefixes equal running − k; a dictionary of prefix → how many times seen answers that instantly.
From brute force to the pattern: Try every start i and every end j and add up the slice: O(n³), or O(n²) if you extend a running sum from each start. The waste is that every slice sum is rebuilt from scratch. With prefix sums a slice sum is one subtraction, and storing the prefixes you've seen in a hash map turns "is there a start that makes this sum k?" into one O(1) lookup, so the whole thing is O(n).
Spot it: "number of subarrays" + "sum equals k" + negatives allowed, so Sliding Window won't work. That points to Prefix Sum + hash map.
Key insight: If running_now − running_earlier = k, then the slice between those two points sums to k. So at every step, count how many earlier prefixes equal running_now − k.
Approach:
- Seed
seen = {0: 1}for the empty prefix. - For each number, add it to
running. - Add
seen[running − k]to the count: each one is a start point that makes a k-sum slice ending here. - Only then record
runninginseen.
from typing import List
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
# seen: running total -> how many times we have reached that total so far
seen = {}
# The empty prefix (before index 0) has total 0. Recording it lets
# a slice that starts at index 0 be counted.
seen[0] = 1
running = 0 # sum of nums[0..i]
count = 0 # the answer
for i in range(len(nums)):
number = nums[i]
running = running + number
# A slice ending at i sums to k exactly when an earlier running
# total equals running - k. Count all such earlier totals.
needed = running - k
if needed in seen:
count = count + seen[needed]
# Record AFTER counting, so we never count an empty slice.
if running not in seen:
seen[running] = 0
seen[running] = seen[running] + 1
return count
What the code is doing, line by line:
seenis a dictionary from "running total" to "how many times we reached it". We seed it withseen[0] = 1for the empty prefix.runningis the sum of everything up to the current index;countis the answer.- In the loop, we read
nums[i]intonumberand add it torunning. needed = running - k: if some earlier running total equals this, the slice after that point up toisums tok. We add how many times we saw it.- Finally we record
runninginseen(create it with 0 if new, then add 1). This happens after counting, so a slice never pairs with itself. - After the loop,
countholds the number of slices that sum tok.
Dry run on the example nums = [1, 2, 3], k = 3:
| i | number | running | needed | added | count | seen after |
|---|---|---|---|---|---|---|
| 0 | 1 | 1 | -2 | 0 | 0 | {0: 1, 1: 1} |
| 1 | 2 | 3 | 0 | 1 | 1 | {0: 1, 1: 1, 3: 1} |
| 2 | 3 | 6 | 3 | 1 | 2 | {0: 1, 1: 1, 3: 1, 6: 1} |
Result: 2. The two slices are [1, 2] (found at i = 1, because total 0 was seen) and [3] (found at i = 2, because total 3 was seen).
Edge cases to test:
k = 0with zeros in the array, e.g.[0, 0, 0]→ 6 subarrays (forgetting to seed{0: 1}gives the wrong count).- Negative numbers, e.g.
[1, -1, 0],k = 0: a sliding window would fail here, prefix sums don't. - A single element equal to
k, e.g.[3],k = 3→ 1.
Complexity: O(n) time, O(n) space.
Medium 525. Contiguous Array Google Meta Amazon Cisco Microsoft
Problem: In a binary array, find the length of the longest subarray with an equal number of 0s and 1s.
nums = [0,1]2nums = [0,1,0]2Constraints: 1 ≤ nums.length ≤ 10⁵ · nums[i] is either 0 or 1.
Hint 1
Replace every 0 with −1. What does "equal number of 0s and 1s" become in terms of the sum?
Hint 2
A subarray with sum 0 sits between two equal prefix sums. To get the longest, remember the first index each prefix sum appeared.
From brute force to the pattern: Check every subarray and count its 0s and 1s: O(n²). Mapping 0 → −1 turns "balanced" into "sums to 0", and two equal prefix sums enclose such a subarray. Storing the first index of each prefix sum lets you measure the longest one in a single O(n) pass.
Spot it: "equal number of" → turn each 0 into −1. Now "equal" means "sum is 0". It's a longest question, so store the first index where each running sum appears.
Approach:
- Map 0 → −1 and 1 → +1, so "equal counts" means "sum 0".
- Store the first index at which each running sum appears, seeded with
{0: -1}. - When a running sum repeats, the slice between the two indices sums to 0. Update the best length.
class Solution:
def findMaxLength(self, nums: List[int]) -> int:
# first_at: running sum -> the FIRST index where we saw it
first_at = {}
# Running sum 0 is "seen" just before index 0 (at index -1).
first_at[0] = -1
running = 0 # (number of 1s) - (number of 0s) so far
best = 0 # longest balanced slice found so far
for i in range(len(nums)):
bit = nums[i]
# Treat 1 as +1 and 0 as -1, so "equal counts" means "sum is 0".
if bit == 1:
running = running + 1
else:
running = running - 1
if running in first_at:
# Same running sum as at index first_at[running]: the slice
# after that index, up to i, has sum 0, so it is balanced.
length = i - first_at[running]
if length > best:
best = length
else:
# First time we see this sum: remember where. Never overwrite
# it, because the earliest index gives the longest slice.
first_at[running] = i
return best
What the code is doing, line by line:
first_atremembers the first index where each running sum appeared.first_at[0] = -1says sum 0 happened "just before index 0".- For each index, a 1 adds 1 to
runningand a 0 subtracts 1. Sorunning= (ones so far) − (zeros so far). - If this
runningwas seen before at indexfirst_at[running], the numbers between that index andiadded up to 0: equal 0s and 1s. Its length isi - first_at[running]; keep it if it beatsbest. - If it is new, store
ias its first index. We never overwrite, because an earlier start means a longer slice. - Return
best.
Dry run on the example nums = [0, 1, 1, 1, 1, 1, 0, 0, 0]:
| i | bit | running | what happens | best |
|---|---|---|---|---|
| 0 | 0 | -1 | new sum, store first_at[-1] = 0 | 0 |
| 1 | 1 | 0 | seen at index -1, length = 1 - -1 = 2 | 2 |
| 2 | 1 | 1 | new sum, store first_at[1] = 2 | 2 |
| 3 | 1 | 2 | new sum, store first_at[2] = 3 | 2 |
| 4 | 1 | 3 | new sum, store first_at[3] = 4 | 2 |
| 5 | 1 | 4 | new sum, store first_at[4] = 5 | 2 |
| 6 | 0 | 3 | seen at index 4, length = 6 - 4 = 2 | 2 |
| 7 | 0 | 2 | seen at index 3, length = 7 - 3 = 4 | 4 |
| 8 | 0 | 1 | seen at index 2, length = 8 - 2 = 6 | 6 |
Result: 6 (indices 3..8: [1, 1, 1, 0, 0, 0]).
Edge cases to test:
- No balanced subarray, e.g.
[0, 0, 0]→ 0. - The whole array is balanced, e.g.
[0, 1, 1, 0]→ 4 (needs the seed{0: −1}). - Length 1 → always 0.
Complexity: O(n) time, O(n) space.
Medium 974. Subarray Sums Divisible by K Google Meta Amazon Microsoft
Problem: Return the number of non-empty contiguous subarrays of nums whose sum is divisible by k.
nums = [4,5,0,-2,-3,1], k = 57nums = [5], k = 90Constraints: 1 ≤ nums.length ≤ 3 * 10⁴ · -10⁴ ≤ nums[i] ≤ 10⁴ · 2 ≤ k ≤ 10⁴
Hint 1
When is prefix[j] − prefix[i] divisible by k? Think about remainders.
Hint 2
Two prefixes with the same remainder mod k enclose a divisible slice. Count prefixes per remainder; each new prefix pairs with all earlier ones of the same remainder.
From brute force to the pattern: Sum every subarray and test % k: O(n²). A slice is divisible exactly when its two bounding prefix sums leave the same remainder, so you only need a count per remainder (an array of size k). Each prefix adds the count of earlier equal remainders: O(n + k).
Spot it: "divisible by k" means two prefixes with the same remainder mod k enclose a divisible slice. Count prefixes by remainder.
Approach:
- Keep counts of prefix remainders mod k, with remainder 0 seen once.
- For each number, update
running = (running + x) % k. - Every earlier prefix with the same remainder forms a divisible slice, so add its count, then increment it.
class Solution:
def subarraysDivByK(self, nums: List[int], k: int) -> int:
# count_mod[r] = how many prefixes so far have remainder r (mod k).
# [0] * k makes a list of k zeros.
count_mod = [0] * k
# The empty prefix has sum 0, so remainder 0 has been seen once.
count_mod[0] = 1
running = 0 # running sum, kept as a remainder mod k
answer = 0
for i in range(len(nums)):
number = nums[i]
# Python's % always gives a result from 0 to k - 1, even for
# negative numbers, so it is safe to use as a list index.
running = (running + number) % k
# Every earlier prefix with the same remainder encloses a slice
# whose sum is divisible by k.
answer = answer + count_mod[running]
# Count this prefix too, for later positions.
count_mod[running] = count_mod[running] + 1
return answer
What the code is doing, line by line:
count_modis a list with one counter per remainder 0..k−1.count_mod[0] = 1counts the empty prefix.runningholds the running sum, but we only keep its remainder modk.- For each number, update the remainder. Python's
%never returns a negative number, so it is a safe index. - Every earlier prefix with the same remainder forms a slice whose sum is divisible by
k, so addcount_mod[running]toanswer. - Then add 1 to
count_mod[running]so later positions can pair with this prefix.
Dry run on the example nums = [4, 5, 0, -2, -3, 1], k = 5:
| i | number | running (remainder) | added (count_mod[running] before) | answer | count_mod after |
|---|---|---|---|---|---|
| 0 | 4 | 4 | 0 | 0 | [1, 0, 0, 0, 1] |
| 1 | 5 | 4 | 1 | 1 | [1, 0, 0, 0, 2] |
| 2 | 0 | 4 | 2 | 3 | [1, 0, 0, 0, 3] |
| 3 | -2 | 2 | 0 | 3 | [1, 0, 1, 0, 3] |
| 4 | -3 | 4 | 3 | 6 | [1, 0, 1, 0, 4] |
| 5 | 1 | 0 | 1 | 7 | [2, 0, 1, 0, 4] |
Result: 7. Notice -2: (4 + -2) % 5 is 2 in Python, never negative.
Edge cases to test:
- Negative numbers: Python's
%already returns a non-negative remainder (-1 % 5 == 4); in other languages addk. - Every element divisible by
k, e.g.[5, 5],k = 5→ 3. k = 1: every subarray counts,n(n + 1) / 2.
Complexity: O(n) time, O(k) space.
Medium 304. Range Sum Query 2D – Immutable Google Amazon
Problem: Design NumMatrix(matrix) with sumRegion(row1, col1, row2, col2), which returns the sum of the rectangle with top-left (row1, col1) and bottom-right (row2, col2) (inclusive). There can be up to 10⁴ queries, each must be O(1).
["NumMatrix", "sumRegion", "sumRegion", "sumRegion"]
[[[[3, 0, 1, 4, 2], [5, 6, 3, 2, 1], [1, 2, 0, 1, 5], [4, 1, 0, 1, 7], [1, 0, 3, 0, 5]]], [2, 1, 4, 3], [1, 1, 2, 2], [1, 2, 2, 4]][null, 8, 11, 12]Constraints: m == matrix.length · n == matrix[i].length · 1 ≤ m, n ≤ 200 · -10⁴ ≤ matrix[i][j] ≤ 10⁴ · 0 ≤ row1 ≤ row2 < m · 0 ≤ col1 ≤ col2 < n · At most 10⁴ calls will be made to sumRegion.
Hint 1
Precompute P[r][c] = sum of everything above and to the left of (r, c). How do you build it from its neighbours?
Hint 2
A rectangle = big prefix − the strip above − the strip to the left + the corner you subtracted twice.
From brute force to the pattern: Adding up the rectangle cell by cell costs O(rows · cols) per query, too slow for many queries. A 2-D prefix sum spends O(rows · cols) once and then answers every query with four lookups (inclusion–exclusion).
Spot it: many rectangle-sum queries on a fixed grid. Use a 2-D prefix sum.
Inclusion–exclusion picture: big rectangle − top strip − left strip + the corner you subtracted twice.
Approach:
- Build
Pwith one extra row and column of zeros. P[r+1][c+1]= cell + top + left − top-left (the overlap was added twice).- Query = big − top strip − left strip + corner.
class NumMatrix:
def __init__(self, matrix: List[List[int]]):
rows = len(matrix)
cols = len(matrix[0])
# self.table has one extra row and one extra column of zeros.
# self.table[row + 1][col + 1] = sum of the rectangle (0, 0) .. (row, col).
self.table = []
for row in range(rows + 1):
# [0] * (cols + 1) makes one row of zeros
self.table.append([0] * (cols + 1))
for row in range(rows):
for col in range(cols):
cell = matrix[row][col]
above = self.table[row][col + 1] # rectangle ending one row up
left = self.table[row + 1][col] # rectangle ending one column left
overlap = self.table[row][col] # inside BOTH above and left
# above + left counts the overlap twice, so take it away once
self.table[row + 1][col + 1] = cell + above + left - overlap
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
big = self.table[row2 + 1][col2 + 1] # (0, 0) .. (row2, col2)
top_strip = self.table[row1][col2 + 1] # all rows above row1
left_strip = self.table[row2 + 1][col1] # all columns left of col1
corner = self.table[row1][col1] # removed twice, add back once
return big - top_strip - left_strip + corner
What the code is doing, line by line:
__init__buildsself.tablewith one extra row and column of zeros, one row at a time withappend.- For each cell, it reads the cell value, the rectangle sum just above (
above), just to the left (left), and the diagonal one (overlap). above + leftcounts the overlap area twice, so the new value iscell + above + left - overlap.sumRegionreads four numbers from the table: the big rectangle from (0, 0) to the bottom-right corner, the strip above the query, the strip to its left, and the top-left corner.- Big − top strip − left strip removes everything outside, but the corner was removed twice, so it is added back once.
Dry run on the example from LeetCode (the 5 × 5 matrix), query sumRegion(2, 1, 4, 3):
First, the table built by __init__ (row 0 and column 0 are the padding zeros):
| table row | col 0 | col 1 | col 2 | col 3 | col 4 | col 5 |
|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 3 | 3 | 4 | 8 | 10 |
| 2 | 0 | 8 | 14 | 18 | 24 | 27 |
| 3 | 0 | 9 | 17 | 21 | 28 | 36 |
| 4 | 0 | 13 | 22 | 26 | 34 | 49 |
| 5 | 0 | 14 | 23 | 30 | 38 | 58 |
Then the query:
| step | value |
|---|---|
| big = table[5][4] | 38 |
| top_strip = table[2][4] | 24 |
| left_strip = table[5][1] | 14 |
| corner = table[2][1] | 8 |
| big - top_strip - left_strip + corner | 8 |
Result: 8.
Edge cases to test:
- A single-cell region (
row1 == row2,col1 == col2). - A region touching row 0 or column 0: the padded extra row/column of zeros avoids index −1.
- Negative values in the matrix.
Complexity: build O(R·C), query O(1).
Hard 1074. Number of Submatrices That Sum to Target
Problem: Return the number of non-empty submatrices of matrix whose elements sum to target.
matrix = [[0,1,0],[1,1,1],[0,1,0]], target = 04matrix = [[1,-1],[-1,1]], target = 05Constraints: 1 ≤ matrix.length ≤ 100 · 1 ≤ matrix[0].length ≤ 100 · -1000 ≤ matrix[i][j] ≤ 1000 · -10^8 ≤ target ≤ 10^8
Hint 1
Fix a top row and a bottom row. What does the band between them look like if you add up each column?
Hint 2
Each band is a 1-D array of column sums, so count its subarrays with sum target using the LeetCode 560 hash-map trick.
From brute force to the pattern: Enumerating all submatrices is O(r² · c²) and summing each one adds more. Fixing the pair of rows reduces the problem to 1-D: squash the columns into sums, then count subarrays summing to target in O(c) with a prefix-sum map. Total O(r² · c).
Spot it: a 2-D version of #560. Fix a pair of rows (top, bottom). Squash the columns between them into a 1-D array of column sums, then run the #560 hash-map trick on it.
Approach:
- Pick a top row and extend the bottom row down one row at a time.
- Keep
col[c]= sum of column c between top and bottom: a 1-D array. - Run the #560 prefix + hash map count on
colfortarget.
class Solution:
def numSubmatrixSumTarget(self, matrix: List[List[int]], target: int) -> int:
rows = len(matrix)
cols = len(matrix[0])
answer = 0
for top in range(rows):
# col_sums[col] = sum of column col from row top down to row bottom.
col_sums = [0] * cols
for bottom in range(top, rows):
# Grow the band by one row: add row bottom into the column sums.
for col in range(cols):
col_sums[col] = col_sums[col] + matrix[bottom][col]
# col_sums is now a 1-D array. Count its slices that sum to
# target with the problem 560 trick.
seen = {}
seen[0] = 1
running = 0
for col in range(cols):
running = running + col_sums[col]
needed = running - target
if needed in seen:
answer = answer + seen[needed]
if running not in seen:
seen[running] = 0
seen[running] = seen[running] + 1
return answer
What the code is doing, line by line:
- The outer loop picks a
toprow and starts a freshcol_sumslist of zeros. - The middle loop moves
bottomdown one row at a time and adds that row intocol_sums. Nowcol_sums[col]is the sum of columncolbetweentopandbottom. - Any sub-matrix that uses exactly rows
top..bottomis a slice ofcol_sums. So we run the problem 560 code oncol_sumswithtarget. - That inner part keeps
seenandrunningexactly like 560, addingseen[needed]toanswereach time. - After every pair of rows has been tried,
answeris the total count.
Dry run on the example matrix = [[0, 1, 0], [1, 1, 1], [0, 1, 0]], target = 0:
| top | bottom | col_sums | slices of col_sums with sum 0 | answer |
|---|---|---|---|---|
| 0 | 0 | [0, 1, 0] | 2 | 2 |
| 0 | 1 | [1, 2, 1] | 0 | 2 |
| 0 | 2 | [1, 3, 1] | 0 | 2 |
| 1 | 1 | [1, 1, 1] | 0 | 2 |
| 1 | 2 | [1, 2, 1] | 0 | 2 |
| 2 | 2 | [0, 1, 0] | 2 | 4 |
Result: 4: the four single-cell zeros in the corners.
Edge cases to test:
- A single cell equal to
target. target = 0with zeros and negatives (the{0: 1}seed matters).- One row or one column: it degenerates to plain LeetCode 560.
Complexity: O(R²·C) time, O(C) space. Make R the smaller dimension.
8. Common mistakes
prefix[r] - prefix[l-1] and crash at l = 0. Always pad.seen[0] = 1This misses subarrays that start at index 0.k = 0.9. Self-check quiz
Count subarrays of [3, -1, 4, -2, 2] that sum to 4.
Longest subarray with sum ≤ S, where every value is positive.
O(1) space.With a 0-padded prefix array, the sum of nums[l..r] is…
10. Practice list (in order)
| # | Problem | Level | Data structure | Twist |
|---|---|---|---|---|
| 303 | Range Sum Query – Immutable | Easy | Warm-up: the plain template | |
| 238 | Product of Array Except Self | Medium | Prefix and suffix products | |
| 560 | Subarray Sum Equals K | Medium | Prefix + hash map (core) | |
| 523 | Continuous Subarray Sum | Medium | Remainders + length ≥ 2 | |
| 525 | Contiguous Array | Medium | 0 → −1, first index | |
| 974 | Subarray Sums Divisible by K | Medium | Count by remainder | |
| 437 | Path Sum III | Medium | Prefix sums along a tree path (DFS) | |
| 304 | Range Sum Query 2D | Medium | 2-D inclusion–exclusion | |
| 1590 | Make Sum Divisible by P | Medium | Shortest slice to remove | |
| 528 | Random Pick with Weight | Medium | Prefix sums turn weights into ranges; binary-search a random number into its range | |
| 930 | Binary Subarrays With Sum | Medium | Subarray Sum Equals K on a 0/1 array | |
| 1109 | Corporate Flight Bookings | Medium | Difference array: +seats at first, −seats after last, then a prefix sum | |
| 1124 | Longest Well-Performing Interval | Medium | +1 / −1 prefix sums; remember the first index of each sum | |
| 1248 | Count Number of Nice Subarrays | Medium | Prefix count of odd numbers + hash map, like Subarray Sum Equals K | |
| 1314 | Matrix Block Sum | Medium | 2-D prefix sums with clamped rectangle corners | |
| 1674 | Minimum Moves to Make Array Complementary | Medium | Difference array over target sums: every pair costs 0, 1 or 2 depending on the range | |
| 2615 | Sum of Distances | Medium | Group indices by value; prefix sums of positions give all distances at once | |
| 3026 | Maximum Good Subarray Sum | Medium | For each end value, remember the smallest prefix sum before each start value | |
| 3070 | Count Submatrices with Top-Left Element and Sum Less Than k | Medium | Every such submatrix is a 2-D prefix rectangle: count prefix sums ≤ k | |
| 3129 | Find All Possible Stable Binary Arrays I | Medium | DP on (zeros used, ones used, last bit); subtract the count that would create a run longer than limit | |
| 3546 | Equal Sum Grid Partition I | Medium | Row and column prefix sums: a cut works when the prefix is exactly half the total | |
| 3737 | Count Subarrays With Majority Element I | Medium | ±1 prefix sums; count earlier prefixes smaller than the current one, updated in O(1) | |
| 825 | Friends Of Appropriate Ages | Medium | Ages are 1..120: count people per age, prefix sums give how many are in (0.5·x + 7, x] for each age x | |
| 2055 | Plates Between Candles | Medium | Snap the query to the nearest candles inside it, then count plates with a prefix sum | |
| 3212 | Count Submatrices With Equal Frequency of X and Y | Medium | Submatrices must contain (0,0), so they're 2D prefixes: track X − Y and X counts as 2D prefix sums | |
| 3653 | XOR After Range Multiplication Queries I | Medium | Small limits (10³ × 10³): apply each query directly with a stepped range | |
| 3756 | Concatenate Non-Zero Digits and Multiply by Sum II | Medium | Prefix hashes of the non-zero digits: x = P[r+1] − P[l]·10^(count) (mod p); digit sums are a plain prefix sum | |
| 2381 | Shifting Letters II | Medium | Difference array of shifts (+1 forward, −1 backward); prefix sums give each letter's total shift | |
| 2906 | Construct Product Matrix | Medium | Flatten the matrix; each cell is (product of everything before) × (product of everything after), mod 12345 | |
| 1074 | Number of Submatrices That Sum to Target | Hard | Squash rows → 560 | |
| 862 | Shortest Subarray with Sum at Least K | Hard | Prefix + monotonic deque | |
| 363 | Max Sum of Rectangle No Larger Than K | Hard | Fix two rows, squash columns, then best subarray sum ≤ k with a sorted list of prefix sums | |
| 798 | Smallest Rotation with Highest Score | Hard | Each element loses its point on one circular range of rotations: record it in a difference array | |
| 3548 | Equal Sum Grid Partition II | Hard | Prefix sums over rows + value counts per side; which cell may be discounted depends on the section's shape | |
| 3699 | Number of ZigZag Arrays I | Hard | DP over the last value and last direction; prefix sums make each transition O(1) | |
| 2488 | Count Subarrays With Median K | Hard | Map >k to +1 and <k to −1; subarrays through k's index with balance 0 or 1 have median k | |
| 3655 | XOR After Range Multiplication Queries II | Hard | Square-root split: big steps by hand, small steps via a stepped multiplicative difference array per k |
Worked solutions for the practice list
Problems 560, 525, 974, 304 and 1074 are solved in section 7. Here are the rest.
Easy 303. Range Sum Query – Immutable Start here Google Meta Amazon Microsoft
Problem: Given a fixed array, answer many sumRange(left, right) calls (inclusive) quickly.
["NumArray", "sumRange", "sumRange", "sumRange"]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]][null, 1, -1, -3]Constraints: 1 ≤ nums.length ≤ 10⁴ · -10⁵ ≤ nums[i] ≤ 10⁵ · 0 ≤ left ≤ right < nums.length · At most 10⁴ calls will be made to sumRange.
Hint 1
Many queries on the same array: what can you precompute once?
Hint 2
With prefix[i] = sum of the first i numbers, the sum of left..right is a single subtraction.
From brute force to the pattern: Summing nums[left..right] per query is O(n) each, so q queries cost O(n · q). Building the prefix array once in O(n) makes every query O(1).
Spot it: "many queries" on an array that never changes. That's textbook Prefix Sum.
- In the constructor, build
prefixwith a leading 0. - For each query, return
prefix[right+1] − prefix[left].
class NumArray:
def __init__(self, nums: List[int]):
# self.prefix[i] = sum of the first i numbers.
# It starts with 0, which means "the sum of nothing".
self.prefix = [0]
running_total = 0
for i in range(len(nums)):
running_total = running_total + nums[i]
# store the running total after element i
self.prefix.append(running_total)
def sumRange(self, left: int, right: int) -> int:
# "right plus one minus left"
return self.prefix[right + 1] - self.prefix[left]
What the code is doing, line by line:
__init__startsself.prefixas[0](sum of nothing) and keeps arunning_total.- For each number, it adds the number to
running_totaland appends the new total. Soself.prefix[i]is the sum of the firstinumbers. sumRangereturnsself.prefix[right + 1] - self.prefix[left]: everything throughright, minus everything beforeleft.
Dry run on the example nums = [-2, 0, 3, -5, 2, -1]:
| i | nums[i] | running_total | self.prefix after |
|---|---|---|---|
| 0 | -2 | -2 | [0, -2] |
| 1 | 0 | -2 | [0, -2, -2] |
| 2 | 3 | 1 | [0, -2, -2, 1] |
| 3 | -5 | -4 | [0, -2, -2, 1, -4] |
| 4 | 2 | -2 | [0, -2, -2, 1, -4, -2] |
| 5 | -1 | -3 | [0, -2, -2, 1, -4, -2, -3] |
| query | computation | result |
|---|---|---|
| sumRange(0, 2) | prefix[3] - prefix[0] = 1 - 0 | 1 |
| sumRange(2, 5) | prefix[6] - prefix[2] = -3 - -2 | -1 |
| sumRange(0, 5) | prefix[6] - prefix[0] = -3 - 0 | -3 |
Edge cases to test:
left == right(a single element).- The whole array (
left = 0,right = n − 1): this is whyprefixhasn + 1entries. - Negative numbers.
Complexity: build O(n), each query O(1), space O(n).
Medium 238. Product of Array Except Self Start here Google Meta Amazon Microsoft
Problem: Return answer[i] = product of every element except nums[i], without using division, in O(n).
Solve it on LeetCode ↗ · Video walkthrough ↗
nums = [1,2,3,4][24,12,8,6]nums = [-1,1,0,-3,3][0,0,9,0,0]Constraints: 2 ≤ nums.length ≤ 10⁵ · -30 ≤ nums[i] ≤ 30 · The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer.
Hint 1
answer[i] = (product of everything to the left of i) × (product of everything to the right).
Hint 2
Build the left products in one pass, then sweep from the right with a running product. No division needed.
From brute force to the pattern: For each i, multiply all other elements: O(n²). Dividing the total product by nums[i] breaks on zeros and is forbidden. Prefix products from the left and suffix products from the right give each answer in O(1), so O(n) overall.
Spot it: "everything except i" = (everything to the left of i) × (everything to the right of i). That's a prefix product times a suffix product.
- Left pass:
answer[i]= product ofnums[0..i-1], starting from 1. - Right pass: keep a running
suffixproduct and multiply it intoanswer[i]while walking from the end. - No extra arrays needed: the output array holds the prefix products.
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
size = len(nums)
# [1] * size makes a list of size ones (1 is the "empty product")
answer = [1] * size
# Pass 1, left to right: answer[i] = product of everything LEFT of i.
left_product = 1
for i in range(size):
answer[i] = left_product
left_product = left_product * nums[i]
# Pass 2, right to left: multiply in the product of everything RIGHT of i.
right_product = 1
i = size - 1
while i >= 0:
answer[i] = answer[i] * right_product
right_product = right_product * nums[i]
i = i - 1
return answer
What the code is doing, line by line:
sizeis how many numbers there are.answerstarts as a list ofsizeones.- Pass 1 walks left to right. Before multiplying, it stores
left_product(the product of everything left ofi) intoanswer[i], then multipliesnums[i]intoleft_product. - Pass 2 walks right to left with a
whileloop:istarts atsize - 1(the last index) and goes down by 1 until it passes 0. - At each step it multiplies
answer[i]byright_product(the product of everything right ofi), then multipliesnums[i]intoright_product. - Now
answer[i]= (left product) × (right product) = everything exceptnums[i], with no division.
Dry run on the example nums = [1, 2, 3, 4]:
| pass | i | nums[i] | product after this step | answer |
|---|---|---|---|---|
| 1 (left to right) | 0 | 1 | left_product = 1 | [1, 1, 1, 1] |
| 1 (left to right) | 1 | 2 | left_product = 2 | [1, 1, 1, 1] |
| 1 (left to right) | 2 | 3 | left_product = 6 | [1, 1, 2, 1] |
| 1 (left to right) | 3 | 4 | left_product = 24 | [1, 1, 2, 6] |
| 2 (right to left) | 3 | 4 | right_product = 4 | [1, 1, 2, 6] |
| 2 (right to left) | 2 | 3 | right_product = 12 | [1, 1, 8, 6] |
| 2 (right to left) | 1 | 2 | right_product = 24 | [1, 12, 8, 6] |
| 2 (right to left) | 0 | 1 | right_product = 24 | [24, 12, 8, 6] |
Result: [24, 12, 8, 6].
Edge cases to test:
- One zero, e.g.
[1, 2, 0, 4]→ only index 2 is non-zero. - Two zeros → every answer is 0.
- Negative numbers, e.g.
[-1, 1, 0, -3, 3].
Complexity: O(n) time, O(1) extra space (not counting the output).
Medium 523. Continuous Subarray Sum Google Microsoft
Problem: Return true if there is a subarray of length ≥ 2 whose sum is a multiple of k.
nums = [23,2,4,6,7], k = 6truenums = [23,2,6,4,7], k = 6trueConstraints: 1 ≤ nums.length ≤ 10⁵ · 0 ≤ nums[i] ≤ 10⁹ · 0 ≤ sum(nums[i]) ≤ 2³¹ - 1 · 1 ≤ k ≤ 2³¹ - 1
Hint 1
Like LeetCode 974, compare prefix remainders mod k. What's different here?
Hint 2
The subarray must have length at least 2, so store the first index of each remainder and check the distance.
From brute force to the pattern: Checking every subarray of length ≥ 2 is O(n²). Equal remainders mod k mean the slice between them is a multiple of k; remembering where each remainder first appeared lets one pass check the length condition in O(1).
Spot it: "multiple of k" = same remainder trick as #974. "Length ≥ 2" means you must store the first index of each remainder and check the distance.
- Keep
first_at = {0: -1}: remainder 0 is seen "before" index 0. - Walk with
running = (running + x) % k. - If this remainder was seen at index
jandi − j ≥ 2, return true. - Otherwise record the remainder only the first time you see it, to keep the longest distance.
class Solution:
def checkSubarraySum(self, nums: List[int], k: int) -> bool:
# first_at: remainder -> earliest index where the running sum had it
first_at = {}
first_at[0] = -1 # remainder 0 is "seen" just before index 0
running = 0
for i in range(len(nums)):
number = nums[i]
running = (running + number) % k
if running in first_at:
# Same remainder as at index first_at[running]: the slice after
# that index, up to i, has a sum that is a multiple of k.
length = i - first_at[running]
if length >= 2:
return True
else:
# Never overwrite: the earliest index gives the longest slice.
first_at[running] = i
return False
What the code is doing, line by line:
first_atmaps a remainder to the earliest index where the running sum had it.first_at[0] = -1covers slices that start at index 0.- Each step adds the number and keeps only the remainder mod
k. - If that remainder was seen before, the slice between has a sum that is a multiple of
k. Its length isi - first_at[running]; if that is at least 2, returnTrue. - If the remainder is new, store the index. Never overwrite, so we always measure from the earliest index.
- If the loop ends with no success, return
False.
Dry run on the example nums = [23, 2, 4, 6, 7], k = 6:
| i | number | running (remainder) | what happens | result |
|---|---|---|---|---|
| 0 | 23 | 5 | new remainder, store first_at[5] = 0 | keep going |
| 1 | 2 | 1 | new remainder, store first_at[1] = 1 | keep going |
| 2 | 4 | 5 | seen at index 0, length 2 >= 2 | return True |
Result: True: the slice [2, 4] (indices 1..2) sums to 6.
Edge cases to test:
[0, 0]with anyk→ True (0 is a multiple of everyk).- A single element that is a multiple of
k, e.g.[6],k = 6→ False (length 1). - Seeding
{0: −1}so a valid prefix starting at index 0 is found.
Complexity: O(n) time, O(min(n, k)) space.
Medium 437. Path Sum III Google Amazon
Problem: Count downward paths in a binary tree (from any node to any descendant) whose values sum to targetSum.
root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 83root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 223Constraints: The number of nodes in the tree is in the range [0, 1000]. · -10⁹ ≤ Node.val ≤ 10⁹ · -1000 ≤ targetSum ≤ 1000
Hint 1
Along any root-to-node path, a downward path with sum target is a difference of two prefix sums on that path.
Hint 2
DFS with a count map of the prefix sums on the current path; add the node's sum on the way down and remove it on the way back up.
From brute force to the pattern: Starting a DFS from every node and following every downward path is O(n²) on a skewed tree. Treating each root-to-node path like an array, the prefix-sum hash map counts matching paths ending at each node in O(1), so O(n) total.
Spot it: a root-to-node path is just an array. Count "subarrays with sum = target" along it, which is #560, done during a DFS.
- DFS from the root, carrying the running sum from the root to the current node.
- At each node, add
seen[running − target]to the answer. - Add
runningtoseen, recurse into both children, then remove it on the way back (backtrack). This keeps only the current path's prefixes in the map.
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
# self.seen: running sum from the root -> how many nodes on the
# CURRENT path (root down to here) have that running sum.
self.seen = {}
self.seen[0] = 1 # the empty path above the root
self.target = targetSum
return self.count_paths(root, 0)
# Helper method (a separate method instead of a nested function).
# Returns how many good paths end somewhere in the subtree of node.
def count_paths(self, node, running):
if node is None:
return 0
running = running + node.val
# Paths that END at this node: earlier prefixes equal to running - target.
needed = running - self.target
count = 0
if needed in self.seen:
count = self.seen[needed]
# Make this prefix visible to the nodes below.
if running not in self.seen:
self.seen[running] = 0
self.seen[running] = self.seen[running] + 1
count = count + self.count_paths(node.left, running)
count = count + self.count_paths(node.right, running)
# Backtrack: we are leaving this node, so remove its prefix again.
self.seen[running] = self.seen[running] - 1
return count
What the code is doing, line by line:
pathSumsets up shared state onself: theseendictionary (seeded with{0: 1}) and the target. Then it calls the helper on the root with running sum 0.count_pathsreturns 0 for a missing child (node is None).- Otherwise it adds
node.valtorunning, the sum from the root down to this node. - Paths that end here and sum to the target start right after an ancestor whose running sum was
running - target.self.seen[needed]says how many such ancestors there are. - It records
runninginself.seenso the children can use it, then adds the counts from the left and right subtrees. - Before returning, it subtracts 1 from
self.seen[running](backtracking), so sibling branches do not see this node's total.
Dry run on the example root = [10, 5, -3, 3, 2, null, 11, 3, -2, null, 1], targetSum = 8. Rows are in DFS visit order (node, then its left subtree, then its right subtree):
| visit node | current path | running | needed = running - 8 | paths ending here | seen on the path (after adding) |
|---|---|---|---|---|---|
| 10 | 10 | 10 | 2 | 0 | {0: 1, 10: 1} |
| 5 | 10 → 5 | 15 | 7 | 0 | {0: 1, 10: 1, 15: 1} |
| 3 | 10 → 5 → 3 | 18 | 10 | 1 | {0: 1, 10: 1, 15: 1, 18: 1} |
| 3 | 10 → 5 → 3 → 3 | 21 | 13 | 0 | {0: 1, 10: 1, 15: 1, 18: 1, 21: 1} |
| -2 | 10 → 5 → 3 → -2 | 16 | 8 | 0 | {0: 1, 10: 1, 15: 1, 18: 1, 16: 1} |
| 2 | 10 → 5 → 2 | 17 | 9 | 0 | {0: 1, 10: 1, 15: 1, 17: 1} |
| 1 | 10 → 5 → 2 → 1 | 18 | 10 | 1 | {0: 1, 10: 1, 15: 1, 18: 1, 17: 1} |
| -3 | 10 → -3 | 7 | -1 | 0 | {0: 1, 10: 1, 7: 1} |
| 11 | 10 → -3 → 11 | 18 | 10 | 1 | {0: 1, 10: 1, 18: 1, 7: 1} |
Result: 3: the paths 5 → 3, 5 → 2 → 1 and -3 → 11. Notice how seen only ever holds the totals of the current path, thanks to the backtracking step.
Edge cases to test:
- Negative values, so a path can still match after the sum overshoots.
targetSum = 0with zero-valued nodes.- An empty tree → 0.
Complexity: O(n) time, O(h) space (h = tree height).
Medium 1590. Make Sum Divisible by P Google
Problem: Remove the shortest subarray (it may be empty, but you can't remove the whole array) so the sum of what remains is divisible by p. Return its length, or −1.
nums = [3,1,4,2], p = 61nums = [6,3,5,2], p = 92Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹ · 1 ≤ p ≤ 10⁹
Hint 1
Let need = sum(nums) % p. You must remove a subarray whose sum has remainder need.
Hint 2
Remember the latest index of each prefix remainder; at index j look for the remainder (prefix − need) mod p.
From brute force to the pattern: Trying every subarray to remove and re-checking the rest is O(n²) or worse. In remainder terms, you need the shortest slice with sum ≡ need (mod p): a prefix-remainder map storing the latest index finds the shortest one ending at each j in O(1).
Spot it: the removed slice must have sum % p == total % p. That's a remainder lookup, and because it asks for the shortest, store the latest index of each remainder.
need = total % p. If it's 0, return 0.- Walk with
running % p. A slice(j, i]works when(running_i − running_j) % p == need, i.e.running_j ≡ running_i − need. - Look that up in
last_atand keep the minimum length. Always overwritelast_at[running]with the newest index. - If the best length is the whole array, return −1.
class Solution:
def minSubarray(self, nums: List[int], divisor: int) -> int:
# (LeetCode calls the divisor p.)
# Add up all the numbers with a plain loop.
total = 0
for i in range(len(nums)):
total = total + nums[i]
# The removed slice must have this same remainder.
need = total % divisor
if need == 0:
return 0 # already divisible: remove nothing
# last_at: remainder -> LATEST index where a prefix had that remainder
last_at = {}
last_at[0] = -1 # the empty prefix
running = 0
best = len(nums) # means "not found yet" (removing everything is not allowed)
for i in range(len(nums)):
running = (running + nums[i]) % divisor
# An earlier prefix with remainder want encloses a slice whose
# remainder is need.
want = (running - need) % divisor
if want in last_at:
length = i - last_at[want]
if length < best:
best = length
# Always overwrite: the most recent index gives the shortest slice.
last_at[running] = i
if best < len(nums):
return best
else:
return -1
What the code is doing, line by line:
- A plain loop adds up
total.need = total % divisoris the remainder the removed slice must have. If it is 0, nothing needs removing. last_atmaps a remainder to the latest index with that prefix remainder, seeded with{0: -1}.beststarts atlen(nums), which means "nothing valid found" because removing the whole array is not allowed.- Each step updates
running(a remainder) and computeswant = (running - need) % divisor: the remainder an earlier prefix must have so that the slice in between has remainderneed. - If
wantis inlast_at, the slice length isi - last_at[want]; keep it if shorter. - Always overwrite
last_at[running] = i: a more recent start gives a shorter slice. - At the end, return
bestif it was improved, otherwise −1.
Dry run on the example nums = [3, 1, 4, 2], p = 6:
Total is 10, so need = 10 % 6 = 4.
| i | nums[i] | running | want | want in last_at? | best | last_at after |
|---|---|---|---|---|---|---|
| 0 | 3 | 3 | 5 | no | 4 | {0: -1, 3: 0} |
| 1 | 1 | 4 | 0 | yes, at index -1, length 2 | 2 | {0: -1, 3: 0, 4: 1} |
| 2 | 4 | 2 | 4 | yes, at index 1, length 1 | 1 | {0: -1, 3: 0, 4: 1, 2: 2} |
| 3 | 2 | 4 | 0 | yes, at index -1, length 4 | 1 | {0: -1, 3: 0, 4: 3, 2: 2} |
Result: 1: remove [4], leaving 3 + 1 + 2 = 6.
Edge cases to test:
- Already divisible (
need = 0) → 0. - Only removing the whole array would work → −1 (the whole array may not be removed).
- Large numbers, so work with remainders throughout.
Complexity: O(n) time, O(min(n, p)) space.
Hard 862. Shortest Subarray with Sum at Least K Google Amazon Microsoft
Problem: The array can contain negatives. Return the length of the shortest subarray with sum ≥ k, or −1.
nums = [1], k = 11nums = [1,2], k = 4-1Constraints: 1 ≤ nums.length ≤ 10⁵ · -10⁵ ≤ nums[i] ≤ 10⁵ · 1 ≤ k ≤ 10⁹
Hint 1
With negatives, a sliding window breaks: shrinking the window can increase its sum. Use prefix sums instead.
Hint 2
Keep a deque of prefix indices with increasing prefix values; pop from the front while the current prefix minus the front is ≥ k.
From brute force to the pattern: Checking all (i, j) pairs of prefix sums is O(n²). A monotonic deque keeps only start candidates that could still give a shorter answer: an older start with a larger prefix is never better, so it gets dropped. Each index enters and leaves once: O(n).
Spot it: because of negatives, Sliding Window fails. With prefix sums the question becomes: for each j, find the latest i < j with P[j] − P[i] ≥ k. A monotonic deque of increasing prefix values answers that in amortized O(1).
- Build
Pwith a leading 0. - Keep a deque of indices whose
Pvalues are strictly increasing. - For each
j: whileP[j] − P[dq[0]] ≥ k, recordj − dq[0]and pop from the front. That start can never give a shorter answer later. - While
P[j] ≤ P[dq[-1]], pop from the back. A later, smaller prefix is always a better start. - Push
j.
from collections import deque
class Solution:
def shortestSubarray(self, nums: List[int], k: int) -> int:
# prefix[i] = sum of the first i numbers (prefix[0] = 0)
prefix = [0]
running = 0
for i in range(len(nums)):
running = running + nums[i]
prefix.append(running)
best = len(nums) + 1 # bigger than any real answer = "not found yet"
# candidates holds start indices whose prefix values strictly increase.
# deque: append(x) adds x at the back, popleft() removes and returns
# the front item, pop() removes the back item.
candidates = deque()
for j in range(len(prefix)):
# Front check: while the oldest start makes a slice with sum >= k,
# record its length and drop it (any later j would only be longer).
while len(candidates) > 0 and prefix[j] - prefix[candidates[0]] >= k:
start = candidates.popleft()
length = j - start
if length < best:
best = length
# Back check: a start whose prefix is >= prefix[j] is never better
# than j (j is later AND has a smaller prefix), so remove it.
last = len(candidates) - 1
while len(candidates) > 0 and prefix[j] <= prefix[candidates[last]]:
candidates.pop()
last = len(candidates) - 1
candidates.append(j)
if best <= len(nums):
return best
else:
return -1
What the code is doing, line by line:
- First build
prefixwith a leading 0 using a running total andappend. beststarts one larger than any real answer.candidatesis a deque of start indices whose prefix values go strictly up from front to back.- For each end index
j, the front loop checks the oldest start: ifprefix[j] - prefix[front] >= k, that slice works. Record its length andpopleft()it, since a laterjcould only make it longer. - The back loop removes starts whose prefix is
>=prefix[j]:jis a later start with a smaller (or equal) prefix, so it is always at least as good.lastis the index of the back item and is recomputed after eachpop(). - Then
jjoins the back of the deque as a future start. - Return
bestif it was improved, otherwise −1.
Dry run on the example nums = [2, -1, 2], k = 3:
prefix = [0, 2, 1, 3].
| j | prefix[j] | popped from front (recorded) | popped from back | candidates after | best |
|---|---|---|---|---|---|
| 0 | 0 | - | - | [0] | 4 |
| 1 | 2 | - | - | [0, 1] | 4 |
| 2 | 1 | - | [1] | [0, 2] | 4 |
| 3 | 3 | [0] | - | [2, 3] | 3 |
Result: 3 (the whole array, 2 - 1 + 2 = 3).
Edge cases to test:
- No valid subarray → −1.
- A single element ≥
k→ 1. - Negatives between positives, e.g.
[2, -1, 2],k = 3→ 3.
Complexity: O(n) time (each index enters and leaves the deque once), O(n) space.
Medium 528. Random Pick with Weight Google Meta Amazon
Problem: Given positive weights w, implement pickIndex() that returns index i with probability w[i] / sum(w).
["Solution","pickIndex"]
[[[1]],[]][null,0]["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"]
[[[1,3]],[],[],[],[],[]][null,1,1,1,1,0]Constraints: 1 ≤ w.length ≤ 10⁴ · 1 ≤ w[i] ≤ 10⁵ · pickIndex will be called at most 10⁴ times.
Hint 1
Imagine laying the weights end to end on a number line.
Hint 2
Store prefix sums; pick a random number up to the total and binary-search which prefix range contains it.
From brute force to the pattern: Repeating each index w[i] times in a list makes picking O(1) but needs memory proportional to the total weight. Prefix sums compress that list into n boundaries, and binary search finds the boundary in O(log n).
Spot it: Stack the weights end to end on a number line: index i owns the stretch [prefix[i], prefix[i + 1]). A random point on the line lands in index i's stretch with exactly the right probability, and finding the stretch is a binary search over prefix sums.
Why not another pattern? Building a list with each index repeated w[i] times is simple but needs sum(w) memory (up to 10⁹). A linear scan over the prefix sums is O(n) per pick; binary search makes it O(log n).
Approach:
- Constructor: build running totals
prefixwhereprefix[i]=w[0] + … + w[i]. pickIndex: draw a random integertargetin1..total.- Binary-search the first index with
prefix[i] ≥ target; that's the answer.
import random
class Solution:
def __init__(self, w: List[int]):
self.prefix = []
running = 0
for weight in w:
running = running + weight
self.prefix.append(running) # index i owns the numbers (prefix[i-1], prefix[i]]
self.total = running
def pickIndex(self) -> int:
target = random.randint(1, self.total) # a random point on the number line
lo = 0
hi = len(self.prefix) - 1
while lo < hi:
mid = (lo + hi) // 2
if self.prefix[mid] < target:
lo = mid + 1 # target lies to the right of mid's range
else:
hi = mid # mid's range might contain target
return lo
What the code is doing, line by line:
self.prefix: running totals; withw = [1, 3]they are[1, 4], so index 0 owns{1}and index 1 owns{2, 3, 4}.random.randint(1, self.total): every integer in1..totalis equally likely, so indexiis hit with probabilityw[i] / total.- The binary search finds the first prefix total that is at least
target: the owner of that number.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- A single weight → always 0.
- One huge weight among tiny ones.
- Equal weights → uniform picks.
Complexity: Constructor O(n); each pickIndex O(log n) time; O(n) space.
Medium 930. Binary Subarrays With Sum Google Nvidia Microsoft
Problem: Count the non-empty subarrays of the binary array nums whose sum equals goal.
nums = [1,0,1,0,1], goal = 24nums = [0,0,0,0,0], goal = 015Constraints: 1 ≤ nums.length ≤ 3 * 10⁴ · nums[i] is either 0 or 1. · 0 ≤ goal ≤ nums.length
Hint 1
A subarray sum is a difference of two prefix sums.
Hint 2
For each prefix, count how many earlier prefixes equal running − goal.
From brute force to the pattern: Summing every subarray is O(n²). With prefix sums, each subarray ending at j corresponds to one earlier prefix, and a count map looks those up in O(1): O(n).
Spot it: "Count subarrays with sum exactly goal" is the prefix-sum + hash-map counting pattern: a subarray ending at j works for every earlier prefix equal to running − goal.
Why not another pattern? A sliding window works for "at most" counts with non-negative numbers (exactly(g) = atMost(g) − atMost(g − 1)) and is also O(n). The prefix-count map handles goal = 0 directly without that trick.
Approach:
seen = {0: 1}counts prefix sums seen so far (the empty prefix included).- For each number, update
running, addseen[running − goal]to the answer, then recordrunning.
class Solution:
def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
seen = {0: 1} # prefix sum -> how many times it has appeared
running = 0
count = 0
for x in nums:
running = running + x
count = count + seen.get(running - goal, 0) # earlier prefixes that start a valid subarray
seen[running] = seen.get(running, 0) + 1
return count
What the code is doing, line by line:
seen = {0: 1}: lets a subarray that starts at index 0 be counted.seen.get(running - goal, 0): each earlier prefix equal torunning − goalmarks the start of a subarray ending here with sumgoal.- Record
runningonly after counting, so a subarray is never empty.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
goal = 0: count the runs of zeros, e.g.[0, 0, 0, 0, 0]→ 15.goallarger than the number of 1s → 0.- All ones.
Complexity: O(n) time, O(n) space.
Medium 1109. Corporate Flight Bookings Google Microsoft
Problem: Each booking [first, last, seats] reserves seats on every flight from first to last (1-indexed). Return the total seats reserved on each of the n flights.
bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5[10,55,45,25,25]bookings = [[1,2,10],[2,2,15]], n = 2[10,25]Constraints: 1 ≤ n ≤ 2 * 10⁴ · 1 ≤ bookings.length ≤ 2 * 10⁴ · bookings[i].length == 3 · 1 ≤ firsti ≤ lasti ≤ n · 1 ≤ seatsi ≤ 10⁴
Hint 1
Each booking adds the same amount to a whole range. Can you record just where it starts and stops?
Hint 2
Write +seats at the start and −seats just after the end, then take a prefix sum.
From brute force to the pattern: Looping over every flight in every booking is O(n · b), up to 4·10¹⁰ operations. The difference array records each booking in O(1) and one prefix sum reconstructs all totals: O(n + b).
Spot it: Many "add x to a whole range" updates followed by one read of every position: the difference array trick, which is a prefix sum in reverse.
Why not another pattern? Adding seats flight by flight for each booking is O(n · bookings). A segment tree supports range updates too, but is unnecessary when all queries come after all updates.
Approach:
diff = [0] * (n + 1).- For each booking:
diff[first − 1] += seatsanddiff[last] −= seats(where the range ends). - A running sum over
diffgives the total for each flight.
class Solution:
def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]:
diff = [0] * (n + 1)
for first, last, seats in bookings:
diff[first - 1] = diff[first - 1] + seats # the range starts here
diff[last] = diff[last] - seats # and stops after flight `last`
answer = []
running = 0
for i in range(n):
running = running + diff[i] # prefix sum of the differences
answer.append(running)
return answer
What the code is doing, line by line:
diff[first - 1] += seats: from flightfirst(0-indexedfirst − 1) onward, addseats.diff[last] -= seats: cancel it after flightlast. The extra slotnabsorbs ranges that end at the last flight.- The running total over
diffrebuilds the actual count for every flight in one pass.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- A booking covering all flights.
- Single-flight bookings (
first == last). - Overlapping bookings add up.
Complexity: O(n + b) time for b bookings, O(n) space.
Medium 1124. Longest Well-Performing Interval Google
Problem: A day is tiring if hours[i] > 8. A well-performing interval has strictly more tiring days than non-tiring days. Return the length of the longest such interval.
hours = [9,9,6,0,6,6,9]3hours = [6,6,6]0Constraints: 1 ≤ hours.length ≤ 10⁴ · 0 ≤ hours[i] ≤ 16
Hint 1
Turn each day into +1 (tiring) or −1. What does "more tiring days" mean for the sum?
Hint 2
Track the first index of every prefix sum; for a non-positive s, look up s − 1.
From brute force to the pattern: Checking all intervals is O(n²). The ±1 transformation makes it a prefix-sum question, and because the sum moves by 1 each step, one lookup of s − 1 finds the best start: O(n).
Spot it: Map tiring days to +1 and others to −1: you want the longest subarray with positive sum. Positive sums of subarrays are differences of prefix sums, and remembering where each prefix value first appeared finds the longest one.
Why not another pattern? A sliding window fails because the sum isn't monotonic when the window grows (values can be −1). Checking every subarray is O(n²).
Key insight: If the running sum s is positive, the whole prefix works. Otherwise the best start is right after the first time the prefix sum was s − 1 (sums change by 1 at a time, so s − 1 is reached before any smaller value).
Approach:
- Scan with
s= running sum of +1/−1. - If
s > 0, the prefix0..iis valid: answeri + 1. - Else if
s − 1has appeared, the interval after its first appearance is valid: lengthi − first[s − 1]. - Record the first index of each prefix sum value.
class Solution:
def longestWPI(self, hours: List[int]) -> int:
first = {} # prefix sum -> first index where it appeared
s = 0
best = 0
for i in range(len(hours)):
s = s + (1 if hours[i] > 8 else -1) # tiring day +1, normal day -1
if s > 0:
best = i + 1 # the whole prefix has more tiring days
elif s - 1 in first:
best = max(best, i - first[s - 1]) # start right after sum s-1 first appeared
if s not in first:
first[s] = i # keep only the earliest index (longest intervals)
return best
What the code is doing, line by line:
sis the prefix sum of the ±1 values.s > 0: the interval from day 0 to dayialready has more tiring days.- Otherwise the sum of
(j, i]iss − prefix(j); it's positive whenprefix(j) ≤ s − 1, and the earliest suchjis wheres − 1first appeared. first[s] = ionly on the first occurrence, so intervals stay as long as possible.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No tiring days → 0.
- All tiring days →
n. - The best interval is in the middle.
Complexity: O(n) time, O(n) space.
Medium 1248. Count Number of Nice Subarrays Google Meta Amazon
Problem: A subarray is nice if it contains exactly k odd numbers. Count the nice subarrays.
nums = [1,1,2,1,1], k = 32nums = [2,4,6], k = 10Constraints: 1 ≤ nums.length ≤ 50000 · 1 ≤ nums[i] ≤ 10^5 · 1 ≤ k ≤ nums.length
Hint 1
Only parity matters: treat odd as 1 and even as 0.
Hint 2
Count subarrays with sum k using prefix sums and a count map.
From brute force to the pattern: Counting odds in every subarray is O(n²). Prefix counts of odd numbers make every subarray's count a subtraction, and a count map finds matching starts in O(1).
Spot it: Replace each number by 1 if odd and 0 if even: you're counting subarrays with sum exactly k, the prefix-count map pattern.
Why not another pattern? A sliding window with exactly(k) = atMost(k) − atMost(k − 1) also works in O(n). Checking all subarrays is O(n²).
Approach:
seen = {0: 1};odds= number of odd values so far.- At each position add
seen[odds − k], then recordodds.
class Solution:
def numberOfSubarrays(self, nums: List[int], k: int) -> int:
seen = {0: 1} # how many prefixes had this many odd numbers
odds = 0
count = 0
for x in nums:
odds = odds + (x % 2) # 1 for odd, 0 for even
count = count + seen.get(odds - k, 0) # starts that leave exactly k odds
seen[odds] = seen.get(odds, 0) + 1
return count
What the code is doing, line by line:
x % 2is 1 for odd numbers, sooddsis a prefix count.- A subarray ending here has exactly
kodds when it starts after a prefix withodds − kodds. - Recording after counting keeps subarrays non-empty.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Not enough odd numbers → 0.
- All odd numbers.
- Even numbers between odd ones multiply the possibilities.
Complexity: O(n) time, O(n) space (an array of size n + 1 works too).
Medium 1314. Matrix Block Sum Google Amazon
Problem: For each cell (i, j), return the sum of all mat[r][c] with |r − i| ≤ k and |c − j| ≤ k (clipped to the grid).
mat = [[1,2,3],[4,5,6],[7,8,9]], k = 1[[12,21,16],[27,45,33],[24,39,28]]mat = [[1,2,3],[4,5,6],[7,8,9]], k = 2[[45,45,45],[45,45,45],[45,45,45]]Constraints: m == mat.length · n == mat[i].length · 1 ≤ m, n, k ≤ 100 · 1 ≤ mat[i][j] ≤ 100
Hint 1
Every answer is a rectangle sum on the same grid.
Hint 2
Precompute a 2-D prefix sum and clamp each block to the grid boundaries.
From brute force to the pattern: Summing a (2k + 1)² block for every cell is O(m · n · k²). One 2-D prefix sum makes every block four lookups: O(m · n) total.
Spot it: Many rectangle sums over one fixed grid: a 2-D prefix sum makes each rectangle O(1).
Why not another pattern? Summing each block directly is O(m · n · k²). A sliding window per row and column also works but is harder to get right.
Approach:
- Build
P[r + 1][c + 1]= sum ofmat[0..r][0..c]. - For each cell, clamp the block to
r1 = max(0, i − k),r2 = min(m − 1, i + k)(same for columns). - Rectangle sum =
P[r2+1][c2+1] − P[r1][c2+1] − P[r2+1][c1] + P[r1][c1].
class Solution:
def matrixBlockSum(self, mat: List[List[int]], k: int) -> List[List[int]]:
m = len(mat)
n = len(mat[0])
P = [[0] * (n + 1) for _ in range(m + 1)] # P[r][c] = sum of mat[0..r-1][0..c-1]
for r in range(m):
for c in range(n):
P[r + 1][c + 1] = mat[r][c] + P[r][c + 1] + P[r + 1][c] - P[r][c]
answer = [[0] * n for _ in range(m)]
for i in range(m):
for j in range(n):
r1 = max(0, i - k) # clamp the block to the grid
r2 = min(m - 1, i + k)
c1 = max(0, j - k)
c2 = min(n - 1, j + k)
answer[i][j] = P[r2 + 1][c2 + 1] - P[r1][c2 + 1] - P[r2 + 1][c1] + P[r1][c1]
return answer
What the code is doing, line by line:
Phas an extra row and column of zeros so rectangles touching the edge need no special case.- Building
P: the cell plus the prefix above plus the prefix to the left, minus the overlap counted twice. - The block is clamped so it never leaves the grid.
- Inclusion–exclusion: big rectangle − strip above − strip to the left + the corner removed twice.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
klarger than the grid → every cell gets the total sum.- A single row or column.
- Corner cells (the block is clipped on two sides).
Complexity: O(m · n) time and space.
Medium 1674. Minimum Moves to Make Array Complementary Google Meta Amazon Microsoft
Problem: Change elements (to values in 1..limit) so that every mirrored pair nums[i] + nums[n − 1 − i] has the same sum. Return the minimum number of changes.
nums = [1,2,4,3], limit = 41nums = [1,2,2,1], limit = 22Constraints: n == nums.length · 2 ≤ n ≤ 10⁵ · 1 ≤ nums[i] ≤ limit ≤ 10⁵ · n is even.
Hint 1
For a fixed target sum, how many changes does one pair need: 0, 1 or 2?
Hint 2
Each pair's cost is constant on a few ranges of target sums; record those ranges in a difference array.
From brute force to the pattern: For every candidate sum T (up to 2·limit), computing every pair's cost is O(n · limit), too slow at 10⁵ × 10⁵. Each pair's cost changes only at 4 breakpoints, so a difference array plus a prefix sum gives the totals for all T in O(n + limit).
Spot it: Try every possible target sum T in 2..2·limit. For one pair (a, b), the cost is 0 if T = a + b, 1 if T is in [min(a,b) + 1, max(a,b) + limit], else 2. Each pair adds a piecewise-constant cost over T: a difference array plus a prefix sum totals it for all T.
Why not another pattern? Trying every target and recomputing every pair's cost is O(n · limit). The difference array is O(n + limit).
Approach:
diffover sums0..2·limit + 1. For each pair(a, b)withlo = min,hi = max:- Every target costs 2 at first:
diff[2] += 2. - Targets in
[lo + 1, hi + limit]cost one less:diff[lo + 1] −= 1,diff[hi + limit + 1] += 1. - The target
a + bcosts one less again:diff[a + b] −= 1,diff[a + b + 1] += 1. - Prefix-sum
diffover2..2·limitand take the minimum.
class Solution:
def minMoves(self, nums: List[int], limit: int) -> int:
n = len(nums)
diff = [0] * (2 * limit + 2)
for i in range(n // 2):
a = nums[i]
b = nums[n - 1 - i]
lo = min(a, b)
hi = max(a, b)
diff[2] = diff[2] + 2 # by default this pair needs 2 changes
diff[lo + 1] = diff[lo + 1] - 1 # from lo+1 one change is enough ...
diff[hi + limit + 1] = diff[hi + limit + 1] + 1 # ... up to hi+limit
diff[a + b] = diff[a + b] - 1 # exactly a+b needs no change
diff[a + b + 1] = diff[a + b + 1] + 1
best = n
moves = 0
for target in range(2, 2 * limit + 1):
moves = moves + diff[target] # prefix sum = total moves for this target sum
best = min(best, moves)
return best
What the code is doing, line by line:
- Each mirrored pair contributes a cost that depends only on the chosen target sum.
- Changing one element of the pair can reach any sum from
lo + 1(shrink the bigger to 1) tohi + limit(grow the smaller tolimit). - The difference array records each pair's three cost levels with five
O(1)updates. - The prefix sum at
targetis the total number of changes for that target; the minimum over all targets is the answer.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Already complementary → 0.
limitsmall, so some sums are unreachable with one change.- Two elements only.
Complexity: O(n + limit) time and space.
Medium 2615. Sum of Distances Google Amazon
Problem: For each index i, return the sum of |i − j| over all other indices j with nums[j] == nums[i].
nums = [1,3,1,1,2][5,0,3,4,0]nums = [0,5,3][0,0,0]Constraints: 1 ≤ nums.length ≤ 10⁵ · 0 ≤ nums[i] ≤ 10⁹
Hint 1
Only equal values interact. Handle each value's list of indices separately.
Hint 2
For a position p, the distances to the left sum to p × (count left) − (sum of left positions); use running sums.
From brute force to the pattern: For each index, scanning all equal indices is O(k²) per group, O(n²) when one value dominates. Running prefix sums of positions compute every answer in O(1).
Spot it: Within one group of equal values (positions sorted), the distance from position p to all positions on its left is p × count_left − sum_left, and to the right sum_right − p × count_right. Running prefix sums of positions give both.
Why not another pattern? Comparing all pairs inside each group is O(n²) when one value repeats a lot.
Approach:
- Group the indices by value (they come out sorted).
- For each group, compute the total of its positions.
- Walk the group keeping
left_sumandleft_count; for positionp: right side =total − left_sum − p,right_count = size − left_count − 1. answer[p] = p·left_count − left_sum + right_sum − p·right_count.
from collections import defaultdict
class Solution:
def distance(self, nums: List[int]) -> List[int]:
groups = defaultdict(list) # value -> its indices in increasing order
for i, x in enumerate(nums):
groups[x].append(i)
answer = [0] * len(nums)
for positions in groups.values():
total = sum(positions)
size = len(positions)
left_sum = 0 # sum of positions to the left of p
for left_count, p in enumerate(positions):
right_sum = total - left_sum - p
right_count = size - left_count - 1
answer[p] = (p * left_count - left_sum) + (right_sum - p * right_count)
left_sum = left_sum + p # p joins the left side
return answer
What the code is doing, line by line:
groups[x]lists the indices holding valuex, already sorted because we scan left to right.enumerate(positions)givesleft_count(how many equal values are to the left) together with the positionp.- Left distances: each of the
left_countpositions isp − q, which sums top·left_count − left_sum. Right distances are the mirror image. left_sum += pafter using it, sopcounts as "left" for the next position.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- A value appearing once → 0.
- All values equal.
- Large indices (answers exceed 32 bits; Python is fine).
Complexity: O(n) time and space.
Medium 3026. Maximum Good Subarray Sum Google Amazon
Problem: A subarray nums[i..j] is good if |nums[i] − nums[j]| == k. Return the maximum sum of a good subarray, or 0 if none exists.
nums = [1,2,3,4,5,6], k = 111nums = [-1,3,2,4,5], k = 311Constraints: 2 ≤ nums.length ≤ 10⁵ · -10⁹ ≤ nums[i] ≤ 10⁹ · 1 ≤ k ≤ 10⁹
Hint 1
Write the subarray sum with prefix sums. For a fixed end, what kind of start do you want?
Hint 2
Map each value to the smallest prefix sum that appeared right before it; look up nums[j] ± k.
From brute force to the pattern: Trying every start for every end is O(n²). With prefix sums, the best start for an end only depends on the minimum prefix before each value, which a hash map keeps in O(1) per step.
Spot it: Subarray sum = prefix[j + 1] − prefix[i]. For a fixed end j, the start must hold value nums[j] ± k, and to maximise the sum you want the smallest prefix[i] among such starts: a hash map from value to the minimum prefix sum before it.
Why not another pattern? Checking every pair (i, j) is O(n²). A sliding window doesn't apply: the condition is about the two endpoint values, not the window's contents.
Approach:
- Keep
prefix(sum ofnums[0..j−1]) andbest_start[v]= smallest prefix sum seen just before an index holdingv. - At index
j: first recordbest_start[nums[j]] = min(..., prefix)(a subarray may start here). - Add
nums[j]toprefix; forvin{nums[j] − k, nums[j] + k}, ifvis in the map, candidate =prefix − best_start[v]. - Return the best candidate, or 0.
class Solution:
def maximumSubarraySum(self, nums: List[int], k: int) -> int:
best_start = {} # value -> smallest prefix sum right before an index with that value
prefix = 0 # sum of nums[0..j-1]
best = None
for x in nums:
if x not in best_start or prefix < best_start[x]:
best_start[x] = prefix # a good subarray could start here
prefix = prefix + x # now prefix = sum of nums[0..j]
for v in (x - k, x + k):
if v in best_start:
candidate = prefix - best_start[v] # sum of nums[i..j] for the best start i
if best is None or candidate > best:
best = candidate
return best if best is not None else 0
What the code is doing, line by line:
best_start[x] = prefixbefore addingx: the subarray starting at this index has sumprefix_after − prefix_before.- Keeping the minimum prefix per value maximises
prefix − best_start[v]. (x - k, x + k): the start value must differ from the end value by exactlyk, in either direction.best is None: sums can be negative, so 0 is only returned when no good subarray exists.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No good subarray → 0.
- All negative numbers (the best sum is negative, e.g.
-6). - The same value repeated (keep the smaller prefix).
Complexity: O(n) time, O(n) space.
Medium 3070. Count Submatrices with Top-Left Element and Sum Less Than k Google
Problem: Count submatrices that contain the top-left cell of grid and have sum ≤ k.
grid = [[7,6,3],[6,6,1]], k = 184grid = [[7,2,9],[1,5,0],[2,6,6]], k = 206Constraints: m == grid.length · n == grid[i].length · 1 ≤ n, m ≤ 1000 · 0 ≤ grid[i][j] ≤ 1000 · 1 ≤ k ≤ 10⁹
Hint 1
How many submatrices contain the top-left cell and end at (r, c)?
Hint 2
Exactly one, and its sum is a 2-D prefix sum.
From brute force to the pattern: Summing every rectangle anchored at (0, 0) from scratch is O((m · n)²). The 2-D prefix sum gives each one in O(1).
Spot it: A submatrix containing the top-left cell is exactly the rectangle (0, 0)..(r, c), whose sum is the 2-D prefix sum P[r][c]. So build the 2-D prefix sums and count the ones ≤ k.
Why not another pattern? Summing each candidate rectangle separately is O((m · n)²). No search is needed at all: there's one candidate per cell.
Approach:
- Build prefix sums in place:
P[r][c] = grid[r][c] + P[r−1][c] + P[r][c−1] − P[r−1][c−1]. - Count the cells whose prefix sum is ≤
k.
class Solution:
def countSubmatrices(self, grid: List[List[int]], k: int) -> int:
m = len(grid)
n = len(grid[0])
P = [[0] * (n + 1) for _ in range(m + 1)] # P[r+1][c+1] = sum of grid[0..r][0..c]
count = 0
for r in range(m):
for c in range(n):
P[r + 1][c + 1] = grid[r][c] + P[r][c + 1] + P[r + 1][c] - P[r][c]
if P[r + 1][c + 1] <= k:
count = count + 1 # the rectangle from (0,0) to (r,c) qualifies
return count
What the code is doing, line by line:
- Each cell
(r, c)defines exactly one submatrix that contains the top-left cell:(0, 0)to(r, c). P[r + 1][c + 1]is that rectangle's sum, built from the neighbours with inclusion–exclusion.- Count it if it's at most
k. (Values are non-negative, so you could even stop a row early.)
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
ksmaller thangrid[0][0]→ 0.- A single cell.
- Every rectangle qualifies →
m · n.
Complexity: O(m · n) time and space.
Medium 3129. Find All Possible Stable Binary Arrays I Google Meta
Problem: Count binary arrays with exactly zero zeros and one ones in which no run of equal bits is longer than limit, modulo 10⁹ + 7.
zero = 1, one = 1, limit = 22zero = 1, one = 2, limit = 11Constraints: 1 ≤ zero, one, limit ≤ 200
Hint 1
Build the array left to right. What must you remember about the end of the array?
Hint 2
Count by (zeros used, ones used, last bit). To forbid runs longer than limit, subtract the arrays that would end in exactly limit + 1 equal bits.
From brute force to the pattern: Listing all arrangements is exponential. A DP that tries every possible length for the last run is O(z · o · limit), about 8·10⁶ steps here. The subtraction (a sliding-window sum over the DP) drops it to O(z · o).
Spot it: Counting arrangements built left to right with a constraint on run length is DP. The "no run longer than limit" rule is handled like a sliding-window sum: add all ways to append this bit, then subtract the ways that end in a run that just exceeded limit.
Why not another pattern? Enumerating arrays is C(zero + one, zero), astronomically many. A DP that also loops over run lengths is O(z · o · limit); the subtraction trick removes that factor.
Key insight: dp0[i][j] = arrays with i zeros, j ones, ending in 0. Appending a 0 to any array with i − 1 zeros gives dp0[i−1][j] + dp1[i−1][j], but that over-counts arrays whose last limit + 1 bits are all 0. Those are exactly the arrays that ended in 1 before that run started: dp1[i − limit − 1][j]. Symmetrically for dp1.
Approach:
- Base cases:
dp0[i][0] = 1for1 ≤ i ≤ limit(only zeros),dp1[0][j] = 1for1 ≤ j ≤ limit. dp0[i][j] = dp0[i−1][j] + dp1[i−1][j] − dp1[i−limit−1][j](wheni > limit).dp1[i][j] = dp0[i][j−1] + dp1[i][j−1] − dp0[i][j−limit−1](whenj > limit).- Answer:
dp0[zero][one] + dp1[zero][one], all modulo10⁹ + 7.
class Solution:
def numberOfStableArrays(self, zero: int, one: int, limit: int) -> int:
MOD = 10 ** 9 + 7
# dp0[i][j] / dp1[i][j]: arrays with i zeros and j ones ending in 0 / 1
dp0 = [[0] * (one + 1) for _ in range(zero + 1)]
dp1 = [[0] * (one + 1) for _ in range(zero + 1)]
for i in range(1, min(zero, limit) + 1):
dp0[i][0] = 1 # only zeros, a run of length i <= limit
for j in range(1, min(one, limit) + 1):
dp1[0][j] = 1 # only ones
for i in range(1, zero + 1):
for j in range(1, one + 1):
ways0 = dp0[i - 1][j] + dp1[i - 1][j] # append a 0 to anything
if i > limit:
ways0 = ways0 - dp1[i - limit - 1][j] # remove arrays now ending in limit+1 zeros
dp0[i][j] = ways0 % MOD
ways1 = dp0[i][j - 1] + dp1[i][j - 1] # append a 1 to anything
if j > limit:
ways1 = ways1 - dp0[i][j - limit - 1] # remove arrays now ending in limit+1 ones
dp1[i][j] = ways1 % MOD
return (dp0[zero][one] + dp1[zero][one]) % MOD
What the code is doing, line by line:
- Two tables split arrays by their last bit, which is what the run-length rule depends on.
- Base cases: an array of only zeros is stable only if its single run is at most
limitlong. ways0appends a 0 to every array with one fewer zero; the subtraction removes exactly those that now end in a run oflimit + 1zeros (they came from arrays ending in 1 withlimit + 1fewer zeros).% MODafter each step keeps numbers small; Python's%also fixes any negative intermediate value.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
limit≥ both counts → every arrangement:C(zero + one, zero).limit = 1→ the bits must alternate.- One count much larger than the other (may give 0).
Complexity: O(zero · one) time and space.
Medium 3546. Equal Sum Grid Partition I Google Microsoft
Problem: Can one horizontal or one vertical cut split grid into two non-empty parts with equal sums?
grid = [[1,4],[2,3]]truegrid = [[1,3],[2,4]]falseConstraints: 1 ≤ m == grid.length ≤ 10⁵ · 1 ≤ n == grid[i].length ≤ 10⁵ · 2 ≤ m * n ≤ 10⁵ · 1 ≤ grid[i][j] ≤ 10⁵
Hint 1
A horizontal cut splits the rows into a prefix and a suffix.
Hint 2
Precompute row and column totals; look for a prefix equal to half of the grand total.
From brute force to the pattern: Summing both sides for each of the m − 1 + n − 1 cuts is O((m + n) · m · n). Row and column totals with running prefixes check every cut in O(1).
Spot it: A horizontal cut after row r splits the total into (sum of rows 0..r) and the rest: a running prefix sum over rows (and over columns for vertical cuts).
Why not another pattern? Recomputing each side's sum for every cut is O(m · n) per cut. Prefix sums over row totals and column totals make each cut O(1).
Approach:
- Compute each row's sum and each column's sum, and the total.
- If the total is odd, return False.
- Running prefix over row sums: if it ever equals
total / 2before the last row, return True. Same for columns.
class Solution:
def canPartitionGrid(self, grid: List[List[int]]) -> bool:
row_sums = [sum(row) for row in grid]
col_sums = [sum(col) for col in zip(*grid)] # zip(*grid) walks the columns
total = sum(row_sums)
if total % 2 == 1:
return False # two equal integer halves are impossible
half = total // 2
for sums in (row_sums, col_sums):
prefix = 0
for i in range(len(sums) - 1): # a cut must leave both parts non-empty
prefix = prefix + sums[i]
if prefix == half:
return True
return False
What the code is doing, line by line:
zip(*grid)transposes the grid, socol_sumshas one total per column.- An odd total can't be split into two equal integers.
range(len(sums) - 1): cutting after the last row/column would leave an empty part.- A prefix equal to half the total means the rest is the other half.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Odd total → False.
- A single row (only vertical cuts possible).
- The equal split exists only vertically.
Complexity: O(m · n) time, O(m + n) space.
Medium 3737. Count Subarrays With Majority Element I Google Meta Amazon Microsoft
Problem: Count the subarrays in which target appears strictly more than half of the time.
nums = [1,2,2,3], target = 25nums = [1,1,1,1], target = 110Constraints: 1 ≤ nums.length ≤ 1000 · 1 ≤ nums[i] ≤ 10⁹ · 1 ≤ target ≤ 10⁹
Hint 1
Turn target into +1 and other values into −1. When is target the majority of a subarray?
Hint 2
Count earlier prefixes smaller than the current prefix; since each step changes the prefix by 1, update that count incrementally.
From brute force to the pattern: Counting target in every subarray is O(n²). The ±1 transformation makes it "count pairs of prefixes with prefix[i] < prefix[j]", and because prefixes move by one step at a time, a running counter answers it in O(n).
Spot it: Map target to +1 and everything else to −1. target is the majority exactly when the subarray's sum is positive, i.e. prefix[j] > prefix[i]. So for each prefix, count the earlier prefixes that are smaller.
Why not another pattern? Checking all O(n²) subarrays with running counts also passes for n ≤ 1000. A Fenwick tree over prefix values gives O(n log n). Because prefix sums move by exactly ±1, a single running counter gives O(n).
Key insight: Keep less = number of earlier prefixes smaller than the current prefix. Moving up by 1 adds the prefixes equal to the old value; moving down by 1 removes the prefixes equal to the new value. A count map of prefix values makes both O(1).
Approach:
count = {0: 1}(the empty prefix),prefix = 0,less = 0.- On
target:less += count[prefix], thenprefix += 1. - Otherwise:
prefix −= 1, thenless −= count[prefix]. - Add
lessto the answer, then recordprefixincount.
from collections import defaultdict
class Solution:
def countMajoritySubarrays(self, nums: List[int], target: int) -> int:
count = defaultdict(int) # prefix value -> how many earlier prefixes had it
count[0] = 1 # the empty prefix
prefix = 0
less = 0 # earlier prefixes strictly smaller than the current prefix
answer = 0
for x in nums:
if x == target:
less = less + count[prefix] # prefixes equal to the old value are now smaller
prefix = prefix + 1
else:
prefix = prefix - 1
less = less - count[prefix] # prefixes equal to the new value are no longer smaller
answer = answer + less # each smaller earlier prefix starts a majority subarray
count[prefix] = count[prefix] + 1
return answer
What the code is doing, line by line:
- With ±1 values, a subarray's sum is
prefix[j] − prefix[i]; positive meanstargetis the strict majority. lesscounts earlier prefixes below the current one, which is exactly the number of valid subarrays ending here.- Going up by one: every earlier prefix that equalled the old value is now strictly smaller, so add their count.
- Going down by one: earlier prefixes equal to the new value stop being smaller, so subtract their count.
- Record the current prefix only after counting, so subarrays are non-empty.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
targetnot present → 0.- All elements equal
target→n(n + 1)/2. - Exactly half is not enough ("strictly more than half").
Complexity: O(n) time, O(n) space.
Hard 363. Max Sum of Rectangle No Larger Than K Google
Problem: Return the largest sum of any rectangle in matrix that is no larger than k (one is guaranteed to exist).
matrix = [[1,0,1],[0,-2,3]], k = 22matrix = [[2,2,-1]], k = 33Constraints: m == matrix.length · n == matrix[i].length · 1 ≤ m, n ≤ 100 · -100 ≤ matrix[i][j] ≤ 100 · -10⁵ ≤ k ≤ 10⁵
Hint 1
Reduce the 2-D problem to 1-D by fixing a pair of rows.
Hint 2
For the 1-D "max subarray sum ≤ k", store previous prefix sums sorted and binary-search for prefix − k.
From brute force to the pattern: Every rectangle with a 2-D prefix sum costs O(r² · c²). Fixing the rows reduces it to 1-D, and the sorted prefix list answers "best earlier start" with a binary search instead of a scan.
Spot it: Rectangle sums → fix a top and bottom row, add up each column between them, and the problem becomes 1-D: the best subarray sum ≤ k. A subarray sum is prefix[j] − prefix[i], so for each prefix[j] you want the smallest earlier prefix ≥ prefix[j] − k: a binary search in a sorted list of prefix sums.
Why not another pattern? Kadane's algorithm finds the maximum subarray sum but can't respect the "≤ k" cap. Enumerating every rectangle with a 2-D prefix sum is O(r² · c²).
Approach:
- For each pair of rows
top ≤ bottom, maintaincol_sums[c]= sum of columncbetween them. - Scan
col_sumswith a running prefix sum; keep earlier prefixes in a sorted list (starting with 0). - For each prefix
p, binary-search the smallest earlier prefixq ≥ p − k;p − qis a candidate ≤k. - Insert
pinto the sorted list and continue; keep the best candidate.
from bisect import bisect_left, insort
class Solution:
def maxSumSubmatrix(self, matrix: List[List[int]], k: int) -> int:
rows = len(matrix)
cols = len(matrix[0])
best = float("-inf")
for top in range(rows):
col_sums = [0] * cols # column sums between top and bottom
for bottom in range(top, rows):
for c in range(cols):
col_sums[c] = col_sums[c] + matrix[bottom][c]
seen = [0] # sorted prefix sums seen so far (0 = empty prefix)
prefix = 0
for value in col_sums:
prefix = prefix + value
# the smallest earlier prefix q with prefix - q <= k
pos = bisect_left(seen, prefix - k)
if pos < len(seen):
best = max(best, prefix - seen[pos])
insort(seen, prefix) # keep the list sorted
return best
What the code is doing, line by line:
topandbottomfix the rows;col_sumsaccumulates asbottommoves down, so it never has to be rebuilt from scratch.- Inside, it's the 1-D problem on
col_sums: a subarray(i, j]has sumprefix − qwhereqis an earlier prefix. bisect_left(seen, prefix - k)finds the smallestq ≥ prefix − k, which gives the largest sum that is still ≤k.insort(seen, prefix)inserts while keeping the list sorted (a balanced tree would make thisO(log c)).
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All negative values.
ksmaller than every positive sum (the answer may be negative).- A single row or column.
Complexity: O(r² · c log c) with a balanced tree (insort on a list is O(c) per insert, fine for these limits).
Hard 798. Smallest Rotation with Highest Score Google
Problem: Rotate nums by k; each element v that ends up at an index ≥ v scores a point. Return the smallest k with the highest score.
nums = [2,3,1,4,0]3nums = [1,3,0,2,4]0Constraints: 1 ≤ nums.length ≤ 10⁵ · 0 ≤ nums[i] < nums.length
Hint 1
For a single element, which rotations make it score? Is that set contiguous (circularly)?
Hint 2
Record where each element stops scoring in a difference array, then take a prefix sum over rotations.
From brute force to the pattern: Scoring each of the n rotations from scratch is O(n²). Each element only changes the score at a couple of rotations, so a difference array accumulates all of them in O(n).
Spot it: Trying every rotation and rescoring is O(n²). For one element, the rotations where it scores form a contiguous circular range, so each element contributes "+1 on a range": a difference array over k, then a prefix sum.
Why not another pattern? Simulating every rotation is quadratic; a segment tree would also work but is overkill for range +1 updates read once at the end.
Key insight: Element i with value v moves to index (i − k) mod n. It stops scoring exactly at rotation k = (i − v + 1) mod n and starts scoring again one step after it reaches index n − 1. So record −1 at (i − v + 1) mod n on top of a baseline of +1 per rotation step; the running total is the score change.
Approach:
change[k]starts at 1 for everyk(each rotation moves one element from index 0 to the back, where it always scores).- For each element:
change[(i − nums[i] + 1) % n] −= 1(the rotation where it drops below its value). - Prefix-sum
change; the index of the maximum is the answer (the first one on ties).
class Solution:
def bestRotation(self, nums: List[int]) -> int:
n = len(nums)
change = [1] * n # each step, the element moved to the back gains a point
for i in range(n):
change[(i - nums[i] + 1) % n] = change[(i - nums[i] + 1) % n] - 1 # rotation where it starts losing its point
for k in range(1, n):
change[k] = change[k] + change[k - 1] # prefix sum: relative score of rotation k
best_k = 0
for k in range(n):
if change[k] > change[best_k]:
best_k = k # strictly better keeps the smallest k on ties
return best_k
What the code is doing, line by line:
changeholds how the score changes from rotationk − 1tok; the baseline 1 is the element wrapping to the end.(i - nums[i] + 1) % n: at this rotation, elementisits one index too low to score, so the score drops by one.- The prefix sum turns changes into relative scores (all shifted by the same constant, which doesn't affect the argmax).
- Strict
>keeps the smallestkamong equal scores.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- A single element → 0.
- Several rotations tie for the best score: return the smallest.
- All zeros: every rotation scores
n.
Complexity: O(n) time and space.
Hard 3548. Equal Sum Grid Partition II Google
Problem: Make one horizontal or vertical cut. The two parts must have equal sums, or become equal after discounting one cell from one part, provided that part stays connected without it. Is it possible?
grid = [[1,4],[2,3]]truegrid = [[1,2],[3,4]]trueConstraints: 1 ≤ m == grid.length ≤ 10⁵ · 1 ≤ n == grid[i].length ≤ 10⁵ · 2 ≤ m * n ≤ 10⁵ · 1 ≤ grid[i][j] ≤ 10⁵
Hint 1
For each cut, prefix sums give both part sums. If they differ by d, which cell would you need to discount?
Hint 2
Keep value counts for both parts. For a part that's only one row or one column wide, only its two end cells can be removed without disconnecting it.
From brute force to the pattern: For every cut, trying every cell and running a connectivity check is O((m · n)²) or worse. Sums come from prefix sums, value lookups from counters, and connectivity from the rectangle's shape, so each cut is O(n) and the whole check is linear.
Spot it: For each cut, prefix sums give both part sums in O(1). The difference d must be 0, or equal to the value of a removable cell in the heavier part. Value counts per part (a hash map updated as the cut moves) answer "is there a cell worth d?" in O(1).
Why not another pattern? Trying every cut and every cell is O((m · n)²). A full connectivity check per removal is unnecessary: the shape of a rectangle tells you which cells are safe.
Key insight: A rectangular part with at least 2 rows and 2 columns stays connected after removing any single cell. A part that is a single row or a single column only stays connected if you remove one of its two end cells. So: count values in each part; for thin parts, check just the two ends.
Approach:
- Handle horizontal cuts on
gridand on its transpose (vertical cuts become horizontal). - Move the cut down one row at a time, keeping the top sum and value counts for top and bottom.
- Let
d = top − bottom.d == 0→ True. Ifd > 0, look for a removable cell worthdin the top part; ifd < 0, one worth−din the bottom part. - Removable: any cell if the part has ≥ 2 rows and ≥ 2 columns; otherwise only the two end cells of that line.
from collections import Counter
class Solution:
def canPartitionGrid(self, grid: List[List[int]]) -> bool:
def check(g):
m = len(g)
n = len(g[0])
total = sum(map(sum, g))
top_count = Counter()
bottom_count = Counter(v for row in g for v in row)
top = 0
for i in range(m - 1): # cut after row i
for v in g[i]:
top_count[v] = top_count[v] + 1 # row i moves from bottom to top
bottom_count[v] = bottom_count[v] - 1
top = top + sum(g[i])
bottom = total - top
d = top - bottom
if d == 0:
return True
if d > 0: # discount a cell worth d from the top part (rows 0..i)
if n == 1:
ends = {g[0][0], g[i][0]} # a single column: only its ends
elif i == 0:
ends = {g[0][0], g[0][n - 1]} # a single row: only its ends
else:
ends = None # a thick rectangle: any cell can go
if (ends is not None and d in ends) or (ends is None and top_count[d] > 0):
return True
else: # discount a cell worth -d from the bottom part (rows i+1..m-1)
need = -d
if n == 1:
ends = {g[i + 1][0], g[m - 1][0]}
elif i + 1 == m - 1:
ends = {g[m - 1][0], g[m - 1][n - 1]}
else:
ends = None
if (ends is not None and need in ends) or (ends is None and bottom_count[need] > 0):
return True
return False
transposed = [list(col) for col in zip(*grid)] # vertical cuts = horizontal cuts of the transpose
return check(grid) or check(transposed)
What the code is doing, line by line:
check(g)tries every horizontal cut; calling it on the transpose covers vertical cuts.- As the cut moves down, row
imoves from the bottom counter to the top counter, andtopgrows by that row's sum. d == 0: equal without discounting anything.- A part with one column or one row is a line; removing a middle cell would split it, so only its two ends may be discounted.
- Otherwise the part is at least 2 × 2 and any cell can be discounted; the counter says whether a cell with the needed value exists.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Equal sums without discounting.
- A single-column grid (only end cells can be discounted).
- The needed value exists but only in the middle of a thin part → not allowed.
Complexity: O(m · n) time and space (each row moves between the counters once, for each of the two orientations).
Hard 3699. Number of ZigZag Arrays I Google Amazon Microsoft
Problem: Count arrays of length n with values in [l, r] where adjacent values differ and no three consecutive values are strictly increasing or strictly decreasing (so the direction must alternate), modulo 10⁹ + 7.
n = 3, l = 4, r = 52n = 3, l = 1, r = 310Constraints: 3 ≤ n ≤ 2000 · 1 ≤ l < r ≤ 2000
Hint 1
What do you need to know about the end of a partial array to extend it legally?
Hint 2
Track the last value and whether the last step went up or down; the next step must go the other way. Use prefix sums to add up all valid previous values at once.
From brute force to the pattern: Enumerating arrays is exponential, and a DP that loops over every previous value is O(n · m²). Prefix sums of each DP row turn every transition into O(1).
Spot it: A sequence built step by step, where the next value depends on the last value and the last direction: DP. Each transition sums over all smaller (or larger) previous values, which is a prefix sum over the DP row.
Why not another pattern? Summing over all previous values directly is O(n · m²) (8·10⁹ for m = 2000). Prefix sums of the previous row make it O(n · m).
Key insight: Let up[v] = arrays ending at v whose last step went up, and down[v] = last step went down. Directions alternate, so new_up[v] = Σ down[u] for u < v and new_down[v] = Σ up[u] for u > v. Both are prefix/suffix sums of the previous row.
Approach:
- Work with offsets
0..m−1wherem = r − l + 1. - Length 2:
up[v] = v(any smaller first value),down[v] = m − 1 − v. - For each further length, build prefix sums of
downand suffix sums ofupto compute the next row. - Answer:
sum(up) + sum(down)after reaching lengthn.
from itertools import accumulate
class Solution:
def zigZagArrays(self, n: int, l: int, r: int) -> int:
MOD = 10 ** 9 + 7
m = r - l + 1 # number of possible values
up = [v for v in range(m)] # length 2 arrays ending at v after a step up
down = [m - 1 - v for v in range(m)] # ... after a step down
for _ in range(n - 2):
below = [0] + list(accumulate(down)) # below[v] = sum of down[0..v-1]
above = list(accumulate(reversed(up)))[::-1] + [0] # above[v] = sum of up[v..m-1]
new_up = [below[v] % MOD for v in range(m)] # previous step went down, now go up to v
new_down = [above[v + 1] % MOD for v in range(m)] # previous step went up, now go down to v
up = new_up
down = new_down
return (sum(up) + sum(down)) % MOD
What the code is doing, line by line:
- Values only matter relative to each other, so work with
0..m−1. - Length 2: to arrive at
vby going up, the first value is any of thevsmaller ones. below[v]= total ofdownover values smaller thanv(a prefix sum), sonew_up[v]= arrays that just went down and now go up tov.above[v + 1]= total ofupover values larger thanv(a suffix sum), givingnew_down[v].- After
n − 2extensions the arrays have lengthn; add both directions.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
n = 3and only two values, e.g.l = 4,r = 5→ 2.- Large
nandm(keep everything modulo 10⁹ + 7). - Adjacent equal values are never allowed.
Complexity: O(n · m) time, O(m) space.
Medium 825. Friends Of Appropriate Ages Meta
Problem: Person x requests y unless y ≤ 0.5·x + 7, y > x, or (y > 100 and x < 100). Count all requests.
ages = [16,16]2ages = [16,17,18]2Constraints: n == ages.length · 1 ≤ n ≤ 2 * 10⁴ · 1 ≤ ages[i] ≤ 120
Hint 1
Only the ages matter, and there are 120 possible ages.
Hint 2
For a given age, the valid targets form an age range: use prefix sums.
From brute force to the pattern: All pairs: O(n²). Age counting with prefix sums: O(n).
Spot it: Only ages matter and there are just 120 of them. Count people per age; with prefix sums, the number of valid targets for age x is the count in (0.5x + 7, x], minus one for the person themself.
Why not another pattern? Checking all pairs of up to 2·10⁴ people is 4·10⁸ comparisons.
Approach:
count[a]per age;prefix[a]= people aged ≤ a.- For each age
xwith people: ifx ≤ 14nobody qualifies (the range is empty). - Targets =
prefix[x] − prefix[x // 2 + 7] − 1; addcount[x] · targets.
class Solution:
def numFriendRequests(self, ages: List[int]) -> int:
count = [0] * 121
for a in ages:
count[a] += 1
prefix = [0] * 121 # prefix[a]: people aged <= a
for a in range(1, 121):
prefix[a] = prefix[a - 1] + count[a]
requests = 0
for x in range(15, 121): # for x <= 14, nobody satisfies 0.5x + 7 < y <= x
if count[x]:
targets = prefix[x] - prefix[x // 2 + 7] - 1 # ages in (x/2 + 7, x], minus themself
requests += count[x] * targets
return requests
What the code is doing, line by line:
y > 0.5x + 7for integers meansy ≥ x // 2 + 8, so subtractingprefix[x // 2 + 7]removes everyone too young.- The third rule is implied by
y ≤ x, so it never matters. - Each person of age
xsends to every valid target except themself.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Everyone younger than 15 → 0.
- Many people of the same age.
- Ages above 100.
Complexity: O(n + 120) time, O(120) space.
Medium 2055. Plates Between Candles Amazon
Problem: For each query [l, r], count the plates * lying between two candles | inside s[l..r].
s = "**|**|***|", queries = [[2,5],[5,9]][2,3]s = "***|**|*****|**||**|*", queries = [[1,17],[4,5],[14,17],[5,11],[15,16]][9,0,0,0,0]Constraints: 3 ≤ s.length ≤ 10⁵ · s consists of '*' and '|' characters. · 1 ≤ queries.length ≤ 10⁵ · queries[i].length == 2 · 0 ≤ lefti ≤ righti < s.length
Hint 1
Which plates in [l, r] actually count?
Hint 2
Find the nearest candles inside the query in O(1), then use prefix sums.
From brute force to the pattern: Scanning each query is O(nq); nearest-candle arrays plus prefix sums make it O(n + q).
Spot it: Only plates between the first candle at or after l and the last candle at or before r count. Precompute those nearest candles and a prefix count of plates, then each query is O(1).
Why not another pattern? Scanning each query's substring is O(n · q).
Approach:
plates[i]= plates ins[:i].next_candle[i](scan from the right) andprev_candle[i](from the left).- Query:
a = next_candle[l],b = prev_candle[r]; ifa < b:plates[b] − plates[a], else 0.
class Solution:
def platesBetweenCandles(self, s: str, queries: List[List[int]]) -> List[int]:
n = len(s)
plates = [0] * (n + 1) # plates[i]: '*' in s[:i]
for i, ch in enumerate(s):
plates[i + 1] = plates[i] + (ch == "*")
prev_candle = [-1] * n # nearest '|' at or before i
last = -1
for i, ch in enumerate(s):
if ch == "|":
last = i
prev_candle[i] = last
next_candle = [n] * n # nearest '|' at or after i
nxt = n
for i in range(n - 1, -1, -1):
if s[i] == "|":
nxt = i
next_candle[i] = nxt
answer = []
for l, r in queries:
a, b = next_candle[l], prev_candle[r] # the candles that enclose plates in [l, r]
answer.append(plates[b] - plates[a] if a < b else 0)
return answer
What the code is doing, line by line:
- Plates outside the outermost candles of the query can't be between two candles.
plates[b] − plates[a]counts plates strictly between positionsaandb(both candles).
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No candles in the range → 0.
- Only one candle → 0.
- Queries covering the whole string.
Complexity: O(n + q) time, O(n) space.
Medium 3212. Count Submatrices With Equal Frequency of X and Y Amazon
Problem: Count submatrices that contain grid[0][0], have equally many X and Y, and at least one X.
grid = [["X","Y","."],["Y",".","."]]3grid = [["X","X"],["X","Y"]]0Constraints: 1 ≤ grid.length, grid[i].length ≤ 1000 · grid[i][j] is either 'X', 'Y', or '.'.
Hint 1
Which submatrices contain grid[0][0]?
Hint 2
They're all prefixes: use 2D prefix sums of (X − Y) and X.
From brute force to the pattern: Recounting each of the mn prefix rectangles is O((mn)²); prefix sums make it O(mn).
Spot it: Containing the top-left cell means each submatrix is grid[0..i][0..j], i.e. a 2D prefix. Keep prefix sums of X − Y and of X.
Why not another pattern? Counting each prefix submatrix from scratch is O((mn)²).
Approach:
- Running column sums per row:
diff[j](X − Y) andxs[j](X count) for rows0..i. - Sweep
jleft to right accumulating them into the prefix of rowi. - Count
(i, j)with prefix diff 0 and prefix X > 0.
class Solution:
def numberOfSubmatrices(self, grid: List[List[str]]) -> int:
cols = len(grid[0])
col_diff = [0] * cols # X - Y in column j over rows 0..i
col_x = [0] * cols # number of X in column j over rows 0..i
count = 0
for row in grid:
diff = xs = 0 # totals of the prefix grid[0..i][0..j]
for j, ch in enumerate(row):
col_diff[j] += (ch == "X") - (ch == "Y")
col_x[j] += ch == "X"
diff += col_diff[j]
xs += col_x[j]
if diff == 0 and xs > 0:
count += 1 # balanced and contains an X
return count
What the code is doing, line by line:
- Column totals grow row by row; summing them left to right gives each prefix rectangle.
- Booleans add as 0/1.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No X anywhere → 0.
- One row.
- Balanced but without X (all dots) doesn't count.
Complexity: O(m · n) time, O(n) space.
Medium 3653. XOR After Range Multiplication Queries I Amazon
Problem: Each query multiplies nums[l], nums[l + k], … (up to r) by v mod 10⁹ + 7. Return the XOR of the final array.
nums = [1,1,1], queries = [[0,2,1,4]]4nums = [2,3,1,5,4], queries = [[1,4,2,3],[0,2,1,2]]31Constraints: 1 ≤ n == nums.length ≤ 10³ · 1 ≤ nums[i] ≤ 10⁹ · 1 ≤ q == queries.length ≤ 10³ · queries[i] = [li, ri, ki, vi] · 0 ≤ li ≤ ri < n · 1 ≤ ki ≤ n · 1 ≤ vi ≤ 10⁵
Hint 1
Check the limits before optimising.
Hint 2
Simulate each query directly.
From brute force to the pattern: This is the direct simulation, fast enough here.
Spot it: With n, q ≤ 1000, a direct simulation costs at most 10⁶ updates. (Version II, with 10⁵, needs a stepped multiplicative difference array — see the next problem.)
Why not another pattern? Anything fancier is unnecessary at this size.
Approach:
- For each query, loop
iinrange(l, r + 1, k)and multiply. - XOR all values.
from functools import reduce
from operator import xor
class Solution:
def xorAfterQueries(self, nums: List[int], queries: List[List[int]]) -> int:
MOD = 10**9 + 7
for l, r, k, v in queries:
for i in range(l, r + 1, k): # every k-th index in [l, r]
nums[i] = nums[i] * v % MOD
return reduce(xor, nums)
What the code is doing, line by line:
range(l, r + 1, k)visits exactly the indices the query touches.- XOR isn't compatible with the multiplications, so it's computed once at the end.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
k = 1(every index).k > r − l(one index).- Large products (mod).
Complexity: O(q · n) time in the worst case (k = 1), O(1) extra space.
Medium 3756. Concatenate Non-Zero Digits and Multiply by Sum II Amazon
Problem: For each query, concatenate the non-zero digits of s[l..r] into x and return x · (sum of its digits) mod 10⁹ + 7.
s = "10203004", queries = [[0,7],[1,3],[4,6]][12340, 4, 9]s = "1000", queries = [[0,3],[1,1]][1, 0]Constraints: 1 ≤ m == s.length ≤ 10⁵ · s consists of digits only. · 1 ≤ queries.length ≤ 10⁵ · queries[i] = [li, ri] · 0 ≤ li ≤ ri < m
Hint 1
How would you get the number formed by a range of digits from prefix numbers (like a rolling hash)?
Hint 2
Skip zeros when building the prefix number, and track how many digits were used.
From brute force to the pattern: Concatenating per query is O(n) each; prefix values answer each in O(1).
Spot it: Like a rolling hash: P[i] is the number formed by the non-zero digits of s[:i] (mod p). The digits of a range are P[r + 1] with the prefix P[l] shifted out: subtract P[l] · 10^c where c is how many non-zero digits lie in the range. Prefix sums give c and the digit sum.
Why not another pattern? Building the number per query is O(n) each.
Approach:
- Prefix arrays:
P(number mod p),cnt(non-zero digits),dsum(digit sum), and powers of 10. - Query:
c = cnt[r+1] − cnt[l],x = (P[r+1] − P[l] · 10^c) mod p,sum = dsum[r+1] − dsum[l]. - Answer
x · sum mod p.
class Solution:
def sumAndMultiply(self, s: str, queries: List[List[int]]) -> List[int]:
MOD = 10**9 + 7
n = len(s)
P = [0] * (n + 1) # non-zero digits of s[:i] read as a number, mod MOD
cnt = [0] * (n + 1) # how many non-zero digits in s[:i]
dsum = [0] * (n + 1) # their digit sum
for i, ch in enumerate(s):
d = int(ch)
if d:
P[i + 1] = (P[i] * 10 + d) % MOD
cnt[i + 1] = cnt[i] + 1
else:
P[i + 1] = P[i]
cnt[i + 1] = cnt[i]
dsum[i + 1] = dsum[i] + d
pow10 = [1] * (n + 1)
for i in range(1, n + 1):
pow10[i] = pow10[i - 1] * 10 % MOD
answer = []
for l, r in queries:
c = cnt[r + 1] - cnt[l]
x = (P[r + 1] - P[l] * pow10[c]) % MOD # drop the digits before l
answer.append(x * (dsum[r + 1] - dsum[l]) % MOD)
return answer
What the code is doing, line by line:
- Appending a digit multiplies the number by 10 and adds it, exactly like a polynomial hash.
- The prefix
P[l]has been shifted left bycdigits by positionr + 1, so it's removed withP[l] · 10^c. - Zeros are skipped in
Pbut don't affect the digit sum anyway.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Only zeros in the range → 0.
- Single digit.
- Long ranges (mod arithmetic).
Complexity: O(n + q) time, O(n) space.
Hard 2488. Count Subarrays With Median K Amazon
Problem: nums is a permutation of 1..n. Count subarrays whose median (lower middle for even length) equals k.
nums = [3,2,1,4,5], k = 43nums = [2,3,1], k = 31Constraints: n == nums.length · 1 ≤ n ≤ 10⁵ · 1 ≤ nums[i], k ≤ n · The integers in nums are distinct.
Hint 1
Does the exact value of other elements matter, or only whether they're bigger or smaller than k?
Hint 2
Turn bigger into +1 and smaller into −1. What must the sum of a subarray with median k be?
Hint 3
Every such subarray contains k: count balances to the left and match them on the right.
From brute force to the pattern: Checking every subarray's median is O(n³ log n) (or O(n²) with care); balance counting is O(n).
Spot it: Only whether each value is above or below k matters. With +1 for bigger and −1 for smaller, a subarray containing k has median k exactly when its balance is 0 (odd length) or 1 (even length). Count matching prefix balances on each side of k with a hash map.
Why not another pattern? Sorting every subarray to find its median is O(n³ log n).
Approach:
- Find
p, the index ofk. - Walk left from
p: record how many left extensions give each balance (left[bal], including the empty one with 0). - Walk right from
p: for each right balancer, addleft[−r] + left[1 − r].
from collections import Counter
class Solution:
def countSubarrays(self, nums: List[int], k: int) -> int:
p = nums.index(k)
left = Counter({0: 1}) # balance of nums[i..p-1] for every start i <= p
bal = 0
for i in range(p - 1, -1, -1):
bal += 1 if nums[i] > k else -1
left[bal] += 1
count = 0
bal = 0
for j in range(p, len(nums)): # end of the subarray
if j > p:
bal += 1 if nums[j] > k else -1
count += left[-bal] + left[1 - bal] # total balance 0 (odd length) or 1 (even length)
return count
What the code is doing, line by line:
- Median
kwith odd length means as many values above as belowk: balance 0. - With even length the lower middle is
k, so there is one more value above: balance 1. - Left and right parts combine by adding balances, so each right balance needs a matching left one.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
kat either end.- Even-length subarrays (balance 1).
- n = 1.
Complexity: O(n) time and space.
Hard 3655. XOR After Range Multiplication Queries II Amazon
Problem: Same as version I but n, q ≤ 10⁵: apply all stepped range multiplications, then XOR the array.
nums = [1,1,1], queries = [[0,2,1,4]]4nums = [2,3,1,5,4], queries = [[1,4,2,3],[0,2,1,2]]31Constraints: 1 ≤ n == nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹ · 1 ≤ q == queries.length ≤ 10⁵ · queries[i] = [li, ri, ki, vi] · 0 ≤ li ≤ ri < n · 1 ≤ ki ≤ n · 1 ≤ vi ≤ 10⁵
Hint 1
When k is large, how many indices does a query touch?
Hint 2
For small k, think of a difference array, but with multiplication and a stride of k.
Hint 3
Undo a multiplication with the modular inverse.
From brute force to the pattern: Direct simulation is O(nq); splitting by √n bounds every part by O((n + q)√n).
Spot it: A query with step k ≥ √n touches at most √n indices: apply it directly. For a small k, use a difference array along the progression: multiply diff[l] by v and the first index past the range by v⁻¹, then a prefix product with stride k spreads it.
Why not another pattern? Direct simulation is O(n · q) = 10¹⁰ when k = 1.
Approach:
B = ⌈√n⌉. Largek: loop the indices.- Small
k:diff[k][l] *= v;stop = l + ((r − l) // k + 1) · k; ifstop < n,diff[k][stop] *= v⁻¹(Fermat inverse). - For each used
k:diff[i] *= diff[i − k]left to right, thennums[i] *= diff[i]. - XOR everything.
from collections import defaultdict
from functools import reduce
from operator import xor
class Solution:
def xorAfterQueries(self, nums: List[int], queries: List[List[int]]) -> int:
MOD = 10**9 + 7
n = len(nums)
B = int(n ** 0.5) + 1
diff = defaultdict(lambda: [1] * n) # one stepped difference array per small k
for l, r, k, v in queries:
if k >= B:
for i in range(l, r + 1, k): # at most sqrt(n) indices
nums[i] = nums[i] * v % MOD
else:
d = diff[k]
d[l] = d[l] * v % MOD # start multiplying at l
stop = l + ((r - l) // k + 1) * k # first index of the progression past r
if stop < n:
d[stop] = d[stop] * pow(v, MOD - 2, MOD) % MOD # cancel it from there on
for k, d in diff.items():
for i in range(n):
if i >= k:
d[i] = d[i] * d[i - k] % MOD # prefix product along step k
nums[i] = nums[i] * d[i] % MOD
return reduce(xor, nums)
What the code is doing, line by line:
- A difference array turns "multiply a range" into two point updates; the prefix product (with the same step) rebuilds the multiplier at every index.
- Indices of one progression share
i mod k, so stepping bykkeeps different progressions separate. v⁻¹ = v^(p − 2) mod pbecause p is prime, andv < pso the inverse exists.- Large steps are cheap to do directly, so the work is
O(q √n + n √n).
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All queries with k = 1.
- A range ending exactly at the last index.
- Repeated k values.
Complexity: O((n + q) √n + q log p) time, O(n √n) space in the worst case.
Medium 2381. Shifting Letters II Microsoft
Problem: Apply range shifts (forward or backward, wrapping a–z) to s. Return the final string.
s = "abc", shifts = [[0,1,0],[1,2,1],[0,2,1]]"ace"s = "dztz", shifts = [[0,0,0],[1,1,1]]"catz"Constraints: 1 ≤ s.length, shifts.length ≤ 5 * 10⁴ · shifts[i].length == 3 · 0 ≤ starti ≤ endi < s.length · 0 ≤ directioni ≤ 1 · s consists of lowercase English letters.
Hint 1
Many range updates, one final read: which technique?
Hint 2
Accumulate the shifts with a difference array.
From brute force to the pattern: Per-shift loops are O(nq); the difference array is O(n + q).
Spot it: Many range updates followed by one read → a difference array: mark each range's start and end, then a prefix sum gives every position's total.
Why not another pattern? Applying each shift to every letter in its range is O(n · q).
Approach:
diff[l] += d,diff[r + 1] −= d(d = ±1).- Running sum gives the shift of each letter; apply it mod 26.
class Solution:
def shiftingLetters(self, s: str, shifts: List[List[int]]) -> str:
diff = [0] * (len(s) + 1)
for start, end, direction in shifts:
d = 1 if direction else -1
diff[start] += d # the shift begins here
diff[end + 1] -= d # and stops after end
out = []
total = 0
for i, ch in enumerate(s):
total += diff[i] # net shift for position i
out.append(chr((ord(ch) - 97 + total) % 26 + 97))
return "".join(out)
What the code is doing, line by line:
- The prefix sum of the difference array is the total shift at each index.
- Python's
%wraps negative shifts correctly.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Wrap-around (z → a).
- Net zero shifts.
- Single letter.
Complexity: O(n + q) time, O(n) space.
Medium 2906. Construct Product Matrix Microsoft
Problem: Build a matrix where each cell is the product of all other cells, modulo 12345.
grid = [[1,2],[3,4]][[24,12],[8,6]]grid = [[12345],[2],[1]][[2],[0],[0]]Constraints: 1 ≤ n == grid.length ≤ 10⁵ · 1 ≤ m == grid[i].length ≤ 10⁵ · 2 ≤ n * m ≤ 10⁵ · 1 ≤ grid[i][j] ≤ 10⁹
Hint 1
It's Product of Array Except Self on a flattened matrix.
Hint 2
Why can't you divide by each cell here?
From brute force to the pattern: Multiplying all other cells per cell is O((nm)²); prefix/suffix products are O(nm).
Spot it: Product of everything except self, like Product of Array Except Self: prefix and suffix products over the flattened matrix. Division isn't possible modulo 12345 (not prime), so prefix/suffix it is.
Why not another pattern? Dividing the total by each cell fails: 12345 isn't prime, and cells can be multiples of its factors.
Approach:
- Flatten in row-major order.
- Forward pass:
ans[i] = prefix;prefix *= a[i]. - Backward pass:
ans[i] *= suffix;suffix *= a[i].
class Solution:
def constructProductMatrix(self, grid: List[List[int]]) -> List[List[int]]:
MOD = 12345
n, m = len(grid), len(grid[0])
flat = [x % MOD for row in grid for x in row]
ans = [1] * len(flat)
prefix = 1
for i, x in enumerate(flat):
ans[i] = prefix # product of everything before i
prefix = prefix * x % MOD
suffix = 1
for i in range(len(flat) - 1, -1, -1):
ans[i] = ans[i] * suffix % MOD # times everything after i
suffix = suffix * flat[i] % MOD
return [ans[r * m:(r + 1) * m] for r in range(n)]
What the code is doing, line by line:
- The order of the cells doesn't matter for a product, so the matrix is treated as one array.
- Reducing mod 12345 at every step keeps numbers small.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Cells that are multiples of 12345 (zero products).
- One row.
- 1 × 2 matrix.
Complexity: O(n · m) time and space.