4.3 KiB
1365. How Many Numbers Are Smaller Than the Current Number
Difficulty: Easy URL: https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/ Topics: Array, Hash Table, Sorting, Counting Sort
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 (0–100). 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[a−1] — 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
- Returning
freq[x]instead offreq[x-1]— that counts elements ≤ x (including x itself and its duplicates). Off-by-one between "≤" and "<" is where this pattern bites. - Forgetting the
x === 0edge — smallest possible value has nothing below it. - Sizing the array to
nums.lengthinstead of the value range — the buckets are indexed by value, not position.