Skip to content

Commit daa0c8f

Browse files
CaedenPHgithub-actionspre-commit-ci[bot]cclauss
authored
Create count negative numbers in matrix algorithm (#8813)
* updating DIRECTORY.md * feat: Count negative numbers in sorted matrix * updating DIRECTORY.md * chore: Fix pre-commit * refactor: Combine functions into iteration * style: Reformat reference * feat: Add timings of each implementation * chore: Fix problems with algorithms-keeper bot * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test: Remove doctest from benchmark function * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss <cclauss@me.com> * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss <cclauss@me.com> * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss <cclauss@me.com> * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss <cclauss@me.com> * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss <cclauss@me.com> * Update matrix/count_negative_numbers_in_sorted_matrix.py Co-authored-by: Christian Clauss <cclauss@me.com> * refactor: Use sum instead of large iteration * refactor: Use len not sum * Update count_negative_numbers_in_sorted_matrix.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss <cclauss@me.com>
1 parent 9c9da8e commit daa0c8f

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed

DIRECTORY.md

+2
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@
679679
## Matrix
680680
* [Binary Search Matrix](matrix/binary_search_matrix.py)
681681
* [Count Islands In Matrix](matrix/count_islands_in_matrix.py)
682+
* [Count Negative Numbers In Sorted Matrix](matrix/count_negative_numbers_in_sorted_matrix.py)
682683
* [Count Paths](matrix/count_paths.py)
683684
* [Cramers Rule 2X2](matrix/cramers_rule_2x2.py)
684685
* [Inverse Of Matrix](matrix/inverse_of_matrix.py)
@@ -753,6 +754,7 @@
753754
* [Potential Energy](physics/potential_energy.py)
754755
* [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py)
755756
* [Shear Stress](physics/shear_stress.py)
757+
* [Speed Of Sound](physics/speed_of_sound.py)
756758

757759
## Project Euler
758760
* Problem 001
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
Given an matrix of numbers in which all rows and all columns are sorted in decreasing
3+
order, return the number of negative numbers in grid.
4+
5+
Reference: https://leetcode.com/problems/count-negative-numbers-in-a-sorted-matrix
6+
"""
7+
8+
9+
def generate_large_matrix() -> list[list[int]]:
10+
"""
11+
>>> generate_large_matrix() # doctest: +ELLIPSIS
12+
[[1000, ..., -999], [999, ..., -1001], ..., [2, ..., -1998]]
13+
"""
14+
return [list(range(1000 - i, -1000 - i, -1)) for i in range(1000)]
15+
16+
17+
grid = generate_large_matrix()
18+
test_grids = (
19+
[[4, 3, 2, -1], [3, 2, 1, -1], [1, 1, -1, -2], [-1, -1, -2, -3]],
20+
[[3, 2], [1, 0]],
21+
[[7, 7, 6]],
22+
[[7, 7, 6], [-1, -2, -3]],
23+
grid,
24+
)
25+
26+
27+
def validate_grid(grid: list[list[int]]) -> None:
28+
"""
29+
Validate that the rows and columns of the grid is sorted in decreasing order.
30+
>>> for grid in test_grids:
31+
... validate_grid(grid)
32+
"""
33+
assert all(row == sorted(row, reverse=True) for row in grid)
34+
assert all(list(col) == sorted(col, reverse=True) for col in zip(*grid))
35+
36+
37+
def find_negative_index(array: list[int]) -> int:
38+
"""
39+
Find the smallest negative index
40+
41+
>>> find_negative_index([0,0,0,0])
42+
4
43+
>>> find_negative_index([4,3,2,-1])
44+
3
45+
>>> find_negative_index([1,0,-1,-10])
46+
2
47+
>>> find_negative_index([0,0,0,-1])
48+
3
49+
>>> find_negative_index([11,8,7,-3,-5,-9])
50+
3
51+
>>> find_negative_index([-1,-1,-2,-3])
52+
0
53+
>>> find_negative_index([5,1,0])
54+
3
55+
>>> find_negative_index([-5,-5,-5])
56+
0
57+
>>> find_negative_index([0])
58+
1
59+
>>> find_negative_index([])
60+
0
61+
"""
62+
left = 0
63+
right = len(array) - 1
64+
65+
# Edge cases such as no values or all numbers are negative.
66+
if not array or array[0] < 0:
67+
return 0
68+
69+
while right + 1 > left:
70+
mid = (left + right) // 2
71+
num = array[mid]
72+
73+
# Num must be negative and the index must be greater than or equal to 0.
74+
if num < 0 and array[mid - 1] >= 0:
75+
return mid
76+
77+
if num >= 0:
78+
left = mid + 1
79+
else:
80+
right = mid - 1
81+
# No negative numbers so return the last index of the array + 1 which is the length.
82+
return len(array)
83+
84+
85+
def count_negatives_binary_search(grid: list[list[int]]) -> int:
86+
"""
87+
An O(m logn) solution that uses binary search in order to find the boundary between
88+
positive and negative numbers
89+
90+
>>> [count_negatives_binary_search(grid) for grid in test_grids]
91+
[8, 0, 0, 3, 1498500]
92+
"""
93+
total = 0
94+
bound = len(grid[0])
95+
96+
for i in range(len(grid)):
97+
bound = find_negative_index(grid[i][:bound])
98+
total += bound
99+
return (len(grid) * len(grid[0])) - total
100+
101+
102+
def count_negatives_brute_force(grid: list[list[int]]) -> int:
103+
"""
104+
This solution is O(n^2) because it iterates through every column and row.
105+
106+
>>> [count_negatives_brute_force(grid) for grid in test_grids]
107+
[8, 0, 0, 3, 1498500]
108+
"""
109+
return len([number for row in grid for number in row if number < 0])
110+
111+
112+
def count_negatives_brute_force_with_break(grid: list[list[int]]) -> int:
113+
"""
114+
Similar to the brute force solution above but uses break in order to reduce the
115+
number of iterations.
116+
117+
>>> [count_negatives_brute_force_with_break(grid) for grid in test_grids]
118+
[8, 0, 0, 3, 1498500]
119+
"""
120+
total = 0
121+
for row in grid:
122+
for i, number in enumerate(row):
123+
if number < 0:
124+
total += len(row) - i
125+
break
126+
return total
127+
128+
129+
def benchmark() -> None:
130+
"""Benchmark our functions next to each other"""
131+
from timeit import timeit
132+
133+
print("Running benchmarks")
134+
setup = (
135+
"from __main__ import count_negatives_binary_search, "
136+
"count_negatives_brute_force, count_negatives_brute_force_with_break, grid"
137+
)
138+
for func in (
139+
"count_negatives_binary_search", # took 0.7727 seconds
140+
"count_negatives_brute_force_with_break", # took 4.6505 seconds
141+
"count_negatives_brute_force", # took 12.8160 seconds
142+
):
143+
time = timeit(f"{func}(grid=grid)", setup=setup, number=500)
144+
print(f"{func}() took {time:0.4f} seconds")
145+
146+
147+
if __name__ == "__main__":
148+
import doctest
149+
150+
doctest.testmod()
151+
benchmark()

0 commit comments

Comments
 (0)