Skip to content

Commit c6fed2a

Browse files
committed
LeetCode 315. Count of Smaller Numbers After Self
1 parent c94f5c5 commit c6fed2a

File tree

5 files changed

+293
-0
lines changed

5 files changed

+293
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ Proposed solutions to some LeetCode problems. The first column links to the prob
7272
| [278. First Bad Version][lc278] | 🟢 Easy | [![python](res/py.png)](leetcode/first-bad-version.py) |
7373
| [303. Range Sum Query - Immutable][lc303] | 🟢 Easy | [![python](res/py.png)][lc303py] |
7474
| [304. Range Sum Query 2D - Immutable][lc304] | 🟠 Medium | [![python](res/py.png)][lc304py] |
75+
| [315. Count of Smaller Numbers After Self][lc315] | 🔴 Hard | [![python](res/py.png)][lc315py] |
7576
| [338. Counting Bits][lc338] | 🟢 Easy | [![python](res/py.png)][lc338py] |
7677
| [347. Top K Frequent Elements][lc347] | 🟠 Medium | [![python](res/py.png)][lc347py] |
7778
| [376. Wiggle Subsequence][lc376] | 🟠 Medium | [![python](res/py.png)](leetcode/wiggle-subsequence.py) |
@@ -227,6 +228,8 @@ First column is the problem difficulty, in descending order, second links to the
227228
[lc303py]: leetcode/range-sum-query-immutable.py
228229
[lc304]: https://leetcode.com/problems/range-sum-query-2d-immutable/
229230
[lc304py]: leetcode/range-sum-query-2d-immutable.py
231+
[lc315]: https://leetcode.com/problems/count-of-smaller-numbers-after-self/
232+
[lc315py]: leetcode/count-of-smaller-numbers-after-self.py
230233
[lc338]: https://leetcode.com/problems/counting-bits/
231234
[lc338py]: leetcode/counting-bits.py
232235
[lc347]: https://leetcode.com/problems/top-k-frequent-elements/
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# 315. Count of Smaller Numbers After Self
2+
# 🔴 Hard
3+
#
4+
# https://leetcode.com/problems/count-of-smaller-numbers-after-self/
5+
#
6+
# Tags: Array - Binary Search - Divide and Conquer - Binary Indexed Tree - Segment Tree - Merge Sort - Ordered Set
7+
8+
import timeit
9+
from typing import List
10+
11+
12+
# We can start by the naive brute-force solution, visit each number in nums in order and check how many
13+
# elements after nums are smaller than it. O(n2) - it will fail with Time Limit Exceeded.
14+
#
15+
# Time complexity: O(n^2).
16+
# Space complexity: O(1) - if we don't take into account the input and output arrays.
17+
class Naive:
18+
def countSmaller(self, nums: List[int]) -> List[int]:
19+
res = [0] * len(nums)
20+
for i, num in enumerate(nums):
21+
for val in nums[i + 1 :]:
22+
if val < num:
23+
res[i] += 1
24+
return res
25+
26+
27+
# A specialized binary indexed tree where the update method only increments by 1.
28+
# There is an example of a standard binary indexed tree with comments in `utils/binary_indexed_tree.py`
29+
class BITree:
30+
def __init__(self, n: int):
31+
self.sums = [0] * (n + 1)
32+
33+
def update(self, idx: int) -> None:
34+
idx += 1
35+
while idx < len(self.sums):
36+
self.sums[idx] += 1
37+
idx += idx & -idx
38+
39+
def sum(self, idx: int) -> int:
40+
res = 0
41+
while idx > 0:
42+
res += self.sums[idx]
43+
idx -= idx & -idx
44+
return res
45+
46+
47+
# Use a binary indexed tree to keep count, for each value in the input array, of how many elements equal or smaller
48+
# we have already seen. Then start iterating from the back of the input array, for each position, update the result
49+
# array and add the current value to the binary indexed tree.
50+
#
51+
# Time complexity: O(n*log(n)) - We visit each element on the input array and, for each, update the tree O(log(n)).
52+
# Space complexity: O(n) - The binary indexed tree is stored in memory as an array of size len(nums) + 1.
53+
#
54+
# Runtime: 4187 ms, faster than 54.52% of Python3 online submissions for Count of Smaller Numbers After Self.
55+
# Memory Usage: 34.8 MB, less than 51.30% of Python3 online submissions for Count of Smaller Numbers After Self.
56+
class BITSolution:
57+
def countSmaller(self, nums):
58+
59+
# Create a dictionary of all unique values in the input array as keys pointing to their
60+
# position if they were an ordered set with the smallest at 0 and the biggest at len(n) - 1.
61+
dict = {value: idx for idx, value in enumerate(sorted(set(nums)))}
62+
63+
# Initialize the binary indexed tree, and the results array, to all 0s
64+
bi_tree, res = BITree(len(dict)), [0] * len(nums)
65+
66+
# Start at the back of nums and iterate over every position
67+
for i in range(len(nums) - 1, -1, -1):
68+
69+
# Update the result set with the current sum of frequencies in the tree at that point.
70+
# The tree contains, for each value, the sum of elements smaller than the current one.
71+
res[i] = bi_tree.sum(dict[nums[i]])
72+
73+
# Update the sums on the binary indexed tree. This is a special use case in which we always increment
74+
# by 1 and we are using the dictionary to translate between values and indexes.
75+
# The result is that increasing the value at the index pointed to by the dictionary, we increase the
76+
# numbers equal or less than this value that we have seen up to that moment.
77+
# Since we are iterating from the back, we can use that information to check how many values less than
78+
# the current one we have seen and update the result array.
79+
bi_tree.update(dict[nums[i]])
80+
81+
return res
82+
83+
84+
# TODO look into the segment tree solution.
85+
# https://www.topcoder.com/thrive/articles/Range%20Minimum%20Query%20and%20Lowest%20Common%20Ancestor
86+
class SegmentTreeSol:
87+
def countSmaller(self, nums: List[int]) -> List[int]:
88+
pass
89+
90+
91+
# TODO look into the merge sort solution.
92+
class MergeSortSol:
93+
def countSmaller(self, nums: List[int]) -> List[int]:
94+
pass
95+
96+
97+
def test():
98+
executors = [
99+
# Naive,
100+
BITSolution,
101+
]
102+
tests = [
103+
[
104+
[3, 10, 0, 7, 1, -13, 6, -5, 16, 7, 3, -9],
105+
[5, 9, 3, 6, 3, 0, 3, 1, 3, 2, 1, 0],
106+
],
107+
[[5, 2, 6, 1], [2, 1, 1, 0]],
108+
[[-1], [0]],
109+
[[-1, -1], [0, 0]],
110+
]
111+
for executor in executors:
112+
start = timeit.default_timer()
113+
for _ in range(int(float("1"))):
114+
for col, t in enumerate(tests):
115+
sol = executor()
116+
result = sol.countSmaller(t[0])
117+
exp = t[1]
118+
assert (
119+
result == exp
120+
), f"\033[93m» {result} <> {exp}\033[91m for test {col} using \033[1m{executor.__name__}"
121+
stop = timeit.default_timer()
122+
used = str(round(stop - start, 5))
123+
res = "{0:20}{1:10}{2:10}".format(executor.__name__, used, "seconds")
124+
print(f"\033[92m» {res}\033[0m")
125+
126+
127+
test()

leetcode/lists/binary_indexed_tree.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# List of Binary Indexed Tree problems
2+
3+
A binary indexed tree is a data structure that allows both querying and updating range sums in O(log(n)) time.
4+
5+
| | Level | Name | Solutions |
6+
| :-: | --------- | --------------------------------------------------------------------------------- | -------------------------------------- |
7+
| | 🔴 Hard | [218. The Skyline Problem][lc218] | |
8+
| | 🟠 Medium | [307. Range Sum Query - Mutable][lc307] | |
9+
| | 🔴 Hard | [308. Range Sum Query 2D - Mutable 🔒][lc308] | |
10+
|| 🔴 Hard | [315. Count of Smaller Numbers After Self][lc315] | [![python](../../res/py.png)][lc315py] |
11+
| | 🔴 Hard | [327. Count of Range Sum][lc327] | |
12+
|| 🟠 Medium | [406. Queue Reconstruction by Height][lc406] | [![python](../../res/py.png)][lc406py] |
13+
| | 🔴 Hard | [493. Reverse Pairs][lc493] | |
14+
| | 🟠 Medium | [2031. Count Subarrays With More Ones Than Zeros 🔒][lc2031] | |
15+
| | 🟠 Medium | [673. Number of Longest Increasing Subsequence][lc673] | |
16+
| | 🔴 Hard | [683. K Empty Slot 🔒][lc683] | |
17+
| | 🔴 Hard | [1157. Online Majority Element In Subarray][lc1157] | |
18+
| | 🔴 Hard | [2193. Minimum Number of Moves to Make Palindrome][lc2193] | |
19+
| | 🟠 Medium | [1395. Count Number of Teams][lc1395] | |
20+
| | 🟠 Medium | [1409. Queries on a Permutation With Key][lc1409] | |
21+
| | 🔴 Hard | [1505. Minimum Possible Integer After at Most K Adjacent Swaps On Digits][lc1505] | |
22+
| | 🔴 Hard | [1649. Create Sorted Array through Instructions][lc1649] | |
23+
| | 🟠 Medium | [1756. Design Most Recently Used Queue 🔒][lc1756] | |
24+
| | 🔴 Hard | [1964. Find the Longest Valid Obstacle Course at Each Position][lc1964] | |
25+
| | 🔴 Hard | [2179. Count Good Triplets in an Array][lc2179] | |
26+
| | 🟠 Medium | [2250. Count Number of Rectangles Containing Each Point][lc2250] | |
27+
| | 🟠 Medium | [2286. Booking Concert Tickets in Groups][lc2286] | |
28+
29+
[lc218]: https://leetcode.com/problems/the-skyline-problem/
30+
[lc307]: https://leetcode.com/problems/range-sum-query-mutable/
31+
[lc308]: https://leetcode.com/problems/range-sum-query-2d-mutable/
32+
[lc315]: https://leetcode.com/problems/count-of-smaller-numbers-after-self/
33+
[lc315py]: ../count-of-smaller-numbers-after-self.py
34+
[lc327]: https://leetcode.com/problems/count-of-range-sum/
35+
[lc406]: https://leetcode.com/problems/queue-reconstruction-by-height/
36+
[lc406py]: ../queue-reconstruction-by-height.py
37+
[lc493]: https://leetcode.com/problems/reverse-pairs/
38+
[lc673]: https://leetcode.com/problems/number-of-longest-increasing-subsequence/
39+
[lc683]: https://leetcode.com/problems/k-empty-slots/
40+
[lc1157]: https://leetcode.com/problems/online-majority-element-in-subarray/
41+
[lc1395]: https://leetcode.com/problems/count-number-of-teams/
42+
[lc1409]: https://leetcode.com/problems/queries-on-a-permutation-with-key/
43+
[lc1505]: https://leetcode.com/problems/minimum-possible-integer-after-at-most-k-adjacent-swaps-on-digits/
44+
[lc1649]: https://leetcode.com/problems/create-sorted-array-through-instructions/
45+
[lc1756]: https://leetcode.com/problems/design-most-recently-used-queue/
46+
[lc1964]: https://leetcode.com/problems/find-the-longest-valid-obstacle-course-at-each-position/
47+
[lc2031]: https://leetcode.com/problems/count-subarrays-with-more-ones-than-zeros/
48+
[lc2179]: https://leetcode.com/problems/count-good-triplets-in-an-array/
49+
[lc2193]: https://leetcode.com/problems/minimum-number-of-moves-to-make-palindrome/
50+
[lc2250]: https://leetcode.com/problems/count-number-of-rectangles-containing-each-point/
51+
[lc2286]: https://leetcode.com/problems/booking-concert-tickets-in-groups/
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from utils.binary_indexed_tree import BinaryIndexedTree
2+
3+
4+
# Test the binary indexed tree data structure.
5+
class TestBinaryIndexedTree:
6+
def testCreateFromList(args):
7+
values = [3, 10, 0, 7, 1, -13, 6, -5, 16, 7, 3, -9]
8+
sums = [3, 13, 13, 20, 21, 8, 14, 9, 25, 32, 35, 26]
9+
10+
bit = BinaryIndexedTree.fromList(values)
11+
for i in range(len(values)):
12+
assert bit.getSum(i) == sums[i]
13+
14+
def testGetSum(args):
15+
# Create an empty binary indexed tree with 0 positions.
16+
bit = BinaryIndexedTree(8)
17+
# Add 3 at index 2
18+
bit.update(2, 3)
19+
# Add 4 at index 3
20+
bit.update(3, 4)
21+
22+
assert bit.getSum(2) == 3
23+
assert bit.getSum(3) == 7
24+
25+
def testUpdate(args):
26+
values = [3, 10, 0, 7, 1, -13, 6, -5, 16, 7, 3, -9]
27+
bit = BinaryIndexedTree.fromList(values)
28+
29+
# Update the value at index 9
30+
bit.update(9, -30)
31+
32+
# Expect the sum [0:9] to have been updated.
33+
assert bit.getSum(10) == 5
34+
35+
def testGetRangeSum(args):
36+
values = [3, 10, 0, 7, 1, -13, 6, -5, 16, 7, 3, -9]
37+
bit = BinaryIndexedTree.fromList(values)
38+
assert bit.getRangeSum(4, 8) == -11

utils/binary_indexed_tree.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from __future__ import annotations
2+
3+
from typing import List
4+
5+
6+
# A Binary Indexed Tree, also called Fenwick Tree implementation.
7+
#
8+
# The binary indexed tree performs both insertion and range sum calculations in
9+
# O(n*log(n)) time.
10+
class BinaryIndexedTree:
11+
12+
# Initialize a new instance of the BITree of the given size.
13+
# The sums array will be of size n + 1 because idx 0 is a dummy node that
14+
# acts as parent of all powers of 2.
15+
def __init__(self, size: int) -> None:
16+
self.sums = [0] * (size + 1)
17+
18+
# Construct a binary indexed tree from a given list of values.
19+
def fromList(values: List[int]) -> BinaryIndexedTree:
20+
bit = BinaryIndexedTree(len(values))
21+
for i, val in enumerate(values):
22+
bit.update(i, val)
23+
return bit
24+
25+
# Get the sums of all the list values from 0 to index. O(log(n)).
26+
def getSum(self, idx: int) -> int:
27+
# Start at idx + 1.
28+
idx += 1
29+
30+
# Initialize the total at 0.
31+
total = 0
32+
33+
# Iterate over all the parents of idx adding their content to the sum.
34+
while idx > 0:
35+
total += self.sums[idx]
36+
37+
# Obtain the parent of the current index shifting the last 1 bit.
38+
# index = index - index & (-index)
39+
# For example, starting at 15:
40+
# 15: 1111
41+
# 14: 1110
42+
# 12: 1100
43+
# 8: 1000
44+
# 0
45+
idx -= idx & (-idx)
46+
47+
return total
48+
49+
# Get the sum of a range of values from start (inclusive) to end (exclusive).
50+
def getRangeSum(self, start: int, end: int) -> int:
51+
if not 0 <= start < end:
52+
return 0
53+
return self.getSum(end - 1) - self.getSum(start - 1)
54+
55+
def update(self, idx: int, val: int) -> None:
56+
# Start at idx + 1.
57+
idx += 1
58+
59+
# Visit all parents of index and add the given value to their current value. O(log(n)).
60+
while idx <= len(self.sums):
61+
self.sums[idx] += val
62+
63+
# Obtain the parent of the current index shifting the last 1 bit.
64+
# index = index + index & (-index)
65+
# For example, starting at 1:
66+
# 1: 00001
67+
# 2: 00010
68+
# 4: 00100
69+
# 8: 01000
70+
# 16: 10000
71+
idx += idx & (-idx)
72+
73+
74+
# https://en.wikipedia.org/wiki/Fenwick_tree

0 commit comments

Comments
 (0)