Files
leetcode/Easy/Array/1365.md
T

4.3 KiB
Raw Blame History

The Frequency Array + Prefix Sum Pattern

Solves: LeetCode 1365 — How Many Numbers Are Smaller Than the Current Number Reusable for: Any "count elements below/above X" problem where values live in a small, bounded range.


The One Insight That Makes This Work

If I know how many times each value appears, and I add those counts up from left to right, then prefix[v] tells me how many elements are ≤ v — instantly, for any v.

That's it. Everything below is just executing this idea.


Step 1: Spot the Signal

Read the constraints:

0 <= nums[i] <= 100

Values are bounded to a tiny range (0100). This is the flashing neon sign that says: don't sort, don't nest loops — build a frequency array indexed by value.

Rule of thumb: value range ≤ ~10⁶ and you need counting/ranking? Frequency array.


Step 2: Count Every Value (the "bucket" pass)

Make an array with one slot per possible value. Walk the input once. Each number votes for its own slot.

const freq = new Array(101).fill(0);  // slots for values 0..100
for (const x of nums) freq[x]++;

For nums = [8, 1, 2, 2, 3]:

value:  0  1  2  3  4  5  6  7  8  ...
freq:   0  1  2  1  0  0  0  0  1  ...

Read it as: "one 1, two 2s, one 3, one 8."


Step 3: Prefix Sum (the magic pass)

Now transform freq in place: each slot becomes itself plus everything before it.

for (let i = 1; i < 101; i++) freq[i] += freq[i - 1];

Same example after the pass:

value:  0  1  2  3  4  5  6  7  8  ...
freq:   0  1  3  4  4  4  4  4  5  ...

New meaning: freq[v] = count of elements ≤ v.

  • freq[3] = 4 → four numbers are ≤ 3 (they are 1, 2, 2, 3) ✓
  • freq[7] = 4 → still four numbers ≤ 7 ✓

Step 4: Answer Queries in O(1)

"How many numbers are strictly smaller than x?" is the same question as "how many numbers are ≤ x 1?"

return nums.map((x) => (x === 0 ? 0 : freq[x - 1]));

The x === 0 guard exists because nothing can be smaller than the minimum possible value — and freq[-1] would be undefined.

Trace on [8, 1, 2, 2, 3]:

x lookup answer
8 freq[7] 4
1 freq[0] 0
2 freq[1] 1
2 freq[1] 1
3 freq[2] 3

[4, 0, 1, 1, 3]


Full Solution

var smallerNumbersThanCurrent = function (nums) {
  // 1. Bucket counts
  const freq = new Array(101).fill(0);
  for (const x of nums) freq[x]++;

  // 2. Prefix sum: freq[v] = count of elements <= v
  for (let i = 1; i < 101; i++) freq[i] += freq[i - 1];

  // 3. Strictly smaller than x == count of elements <= x-1
  return nums.map((x) => (x === 0 ? 0 : freq[x - 1]));
};

Complexity: O(n + k) time, O(k) space, where k = value range (101 here). No sort, no log factor.


The Reusable Pattern (memorize this shape)

1. BUCKET   — freq[value]++ for every element
2. PREFIX   — freq[i] += freq[i-1] left to right
3. QUERY    — freq[v] answers "how many ≤ v" in O(1)
              freq[v-1] answers "how many < v"
              n - freq[v] answers "how many > v"

Where else this exact shape shows up

Problem Same pattern, different query
Counting Sort Prefix sums become final sorted positions
LC 315 / rank queries "How many smaller" is literally a rank
LC 1122 Relative Sort Array Bucket + walk buckets in order
Radix sort digit pass Bucket by digit, prefix for placement
Histogram percentiles freq[v] / n = percentile of v
"How many in range [a, b]?" freq[b] freq[a1] — the prefix subtraction trick

The generalization ladder

  • Values bounded and small → frequency array (this pattern)
  • Values huge but few distinct → coordinate compression first, then this pattern
  • Need updates between queries → upgrade prefix array to a Fenwick tree (BIT) — same idea, log-time updates

Common Mistakes

  1. Returning freq[x] instead of freq[x-1] — that counts elements ≤ x (including x itself and its duplicates). Off-by-one between "≤" and "<" is where this pattern bites.
  2. Forgetting the x === 0 edge — smallest possible value has nothing below it.
  3. Sizing the array to nums.length instead of the value range — the buckets are indexed by value, not position.