Skip to content

Commit 05e435f

Browse files
committed
feat: Count negative numbers in sorted matrix
1 parent 2ba4830 commit 05e435f

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"""
2+
Given an `m x n` matrix grid which is sorted in non-increasing order
3+
both row-wise and column-wise, return the number of negative numbers in grid.
4+
5+
> https://leetcode.com/problems/count-negative-numbers-in-a-sorted-matrix
6+
"""
7+
8+
9+
def find_negative_index(array: list[int]):
10+
"""
11+
Find the smallest negative index
12+
13+
>>> find_negative_index([0,0,0,0])
14+
4
15+
>>> find_negative_index([4,3,2,-1])
16+
3
17+
>>> find_negative_index([1,0,-1,-10])
18+
2
19+
>>> find_negative_index([0,0,0,-1])
20+
3
21+
>>> find_negative_index([11,8,7,-3,-5,-9])
22+
3
23+
>>> find_negative_index([-1,-1,-2,-3])
24+
0
25+
>>> find_negative_index([5,1,0])
26+
3
27+
>>> find_negative_index([-5,-5,-5])
28+
0
29+
>>> find_negative_index([0])
30+
1
31+
>>> find_negative_index([])
32+
0
33+
"""
34+
left = 0
35+
right = len(array) - 1
36+
37+
# Edge cases such as no values or
38+
# all numbers are negative
39+
if not array or array[0] < 0:
40+
return 0
41+
42+
while right + 1 > left:
43+
mid = (left + right) // 2
44+
num = array[mid]
45+
46+
# Num must be negative and the index about num
47+
# must be greater than or equal to 0
48+
if num < 0 and array[mid - 1] >= 0:
49+
return mid
50+
51+
if num >= 0:
52+
left = mid + 1
53+
else:
54+
right = mid - 1
55+
# No negative numbers so return the last index
56+
# of the array + 1 which is also the length
57+
return len(array)
58+
59+
60+
def count_negatives_binary_search(grid: list[list[int]]) -> int:
61+
"""
62+
An O(m logn) solution that uses binary search
63+
in order to find the boundary betweem positive and
64+
negative numbers
65+
66+
>>> count_negatives_binary_search(
67+
... [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]])
68+
8
69+
>>> count_negatives_binary_search([[3,2],[1,0]])
70+
0
71+
>>> count_negatives_binary_search([[7,7,6]])
72+
0
73+
>>> count_negatives_binary_search([[7,7,6],[-1,-2,-3]])
74+
3
75+
"""
76+
total = 0
77+
bound = len(grid[0])
78+
79+
for i in range(len(grid)):
80+
bound = find_negative_index(grid[i][:bound])
81+
total += bound
82+
return (len(grid) * len(grid[0])) - total
83+
84+
85+
def count_negatives_brute_force(grid: list[list[int]]) -> int:
86+
"""
87+
This solution is O(n^2) because it iterates through
88+
every column and row.
89+
90+
>>> count_negatives_brute_force(
91+
... [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]])
92+
8
93+
>>> count_negatives_brute_force([[3,2],[1,0]])
94+
0
95+
>>> count_negatives_brute_force([[7,7,6]])
96+
0
97+
>>> count_negatives_brute_force([[7,7,6],[-1,-2,-3]])
98+
3
99+
"""
100+
total = 0
101+
for m in range(len(grid)):
102+
for n in range(len(grid[m])):
103+
if grid[m][n] < 0:
104+
total += 1
105+
return total
106+
107+
108+
def count_negatives_brute_force_with_break(grid: list[list[int]]) -> int:
109+
"""
110+
Similiar to the solution above, however uses break
111+
in order to reduce the number of iterations
112+
113+
>>> count_negatives_brute_force_with_break(
114+
... [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]])
115+
8
116+
>>> count_negatives_brute_force_with_break([[3,2],[1,0]])
117+
0
118+
>>> count_negatives_brute_force_with_break([[7,7,6]])
119+
0
120+
>>> count_negatives_brute_force_with_break([[7,7,6],[-1,-2,-3]])
121+
3
122+
"""
123+
total = 0
124+
length_of_n = len(grid[0])
125+
for m in range(len(grid)):
126+
for index, n in enumerate(range(length_of_n)):
127+
if grid[m][n] < 0:
128+
total += length_of_n - index
129+
break
130+
return total
131+
132+
133+
def generate_large_matrix() -> list[list[int]]:
134+
"""
135+
>>> generate_large_matrix() # doctest: +ELLIPSIS
136+
[[500, ..., -499], [499, ..., -501], ..., [2, ..., -998]]
137+
"""
138+
return [list(range(1000 - i, -1000 - i, -1)) for i in range(1000)]
139+
140+
141+
def benchmark() -> None:
142+
"""Benchmark our functions next to each other"""
143+
from timeit import timeit
144+
145+
print("Running benchmarks")
146+
setup = (
147+
"from __main__ import count_negatives_binary_search, count_negatives_brute_force, "
148+
"count_negatives_brute_force_with_break, generate_large_matrix"
149+
)
150+
151+
cnbs = timeit(
152+
"count_negatives_binary_search(generate_large_matrix())",
153+
setup=setup,
154+
number=5000,
155+
),
156+
157+
print("count_negatives_binary_search()", cnbs[0], "seconds")
158+
print(
159+
"count_negatives_brute_force_with_break()",
160+
timeit(
161+
"count_negatives_brute_force_with_break(generate_large_matrix())",
162+
setup=setup,
163+
number=5000,
164+
),
165+
"seconds",
166+
) #
167+
print(
168+
"count_negatives_brute_force()",
169+
timeit(
170+
"count_negatives_brute_force(generate_large_matrix())",
171+
setup=setup,
172+
number=5000,
173+
),
174+
"seconds",
175+
)
176+
177+
178+
if __name__ == "__main__":
179+
import doctest
180+
181+
doctest.testmod()
182+
183+
benchmark()

0 commit comments

Comments
 (0)