Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create count negative numbers in matrix algorithm #8813

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e14e0fb
updating DIRECTORY.md
Jun 7, 2023
2ba4830
Merge branch 'TheAlgorithms:master' into master
CaedenPH Jun 8, 2023
05e435f
feat: Count negative numbers in sorted matrix
CaedenPH Jun 8, 2023
83d877c
updating DIRECTORY.md
Jun 8, 2023
f0a785c
chore: Fix pre-commit
CaedenPH Jun 8, 2023
8cd5cdc
refactor: Combine functions into iteration
CaedenPH Jun 8, 2023
ea5b2d0
style: Reformat reference
CaedenPH Jun 8, 2023
0e346b2
feat: Add timings of each implementation
CaedenPH Jun 8, 2023
b58a340
chore: Fix problems with algorithms-keeper bot
CaedenPH Jun 8, 2023
3b16704
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 8, 2023
fca6e9d
test: Remove doctest from benchmark function
CaedenPH Jun 8, 2023
02cd4f3
Update matrix/count_negative_numbers_in_sorted_matrix.py
CaedenPH Jun 9, 2023
63c5147
Update matrix/count_negative_numbers_in_sorted_matrix.py
CaedenPH Jun 9, 2023
a55db73
Update matrix/count_negative_numbers_in_sorted_matrix.py
CaedenPH Jun 10, 2023
0d8a616
Update matrix/count_negative_numbers_in_sorted_matrix.py
CaedenPH Jun 10, 2023
9a7719c
Update matrix/count_negative_numbers_in_sorted_matrix.py
CaedenPH Jun 10, 2023
7a2f01e
Update matrix/count_negative_numbers_in_sorted_matrix.py
CaedenPH Jun 10, 2023
97e2017
refactor: Use sum instead of large iteration
CaedenPH Jun 10, 2023
0f21b1b
refactor: Use len not sum
CaedenPH Jun 10, 2023
605be43
Update count_negative_numbers_in_sorted_matrix.py
cclauss Jun 10, 2023
21dbc02
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@
## Matrix
* [Binary Search Matrix](matrix/binary_search_matrix.py)
* [Count Islands In Matrix](matrix/count_islands_in_matrix.py)
* [Count Negative Numbers In Sorted Matrix](matrix/count_negative_numbers_in_sorted_matrix.py)
* [Count Paths](matrix/count_paths.py)
* [Cramers Rule 2X2](matrix/cramers_rule_2x2.py)
* [Inverse Of Matrix](matrix/inverse_of_matrix.py)
Expand Down Expand Up @@ -753,6 +754,7 @@
* [Potential Energy](physics/potential_energy.py)
* [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py)
* [Shear Stress](physics/shear_stress.py)
* [Speed Of Sound](physics/speed_of_sound.py)

## Project Euler
* Problem 001
Expand Down
151 changes: 151 additions & 0 deletions matrix/count_negative_numbers_in_sorted_matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""
Given an matrix of numbers in which all rows and all columns are sorted in decreasing
order, return the number of negative numbers in grid.

Reference: https://leetcode.com/problems/count-negative-numbers-in-a-sorted-matrix
"""


def generate_large_matrix() -> list[list[int]]:
"""
>>> generate_large_matrix() # doctest: +ELLIPSIS
[[1000, ..., -999], [999, ..., -1001], ..., [2, ..., -1998]]
"""
return [list(range(1000 - i, -1000 - i, -1)) for i in range(1000)]


grid = generate_large_matrix()
test_grids = (
[[4, 3, 2, -1], [3, 2, 1, -1], [1, 1, -1, -2], [-1, -1, -2, -3]],
[[3, 2], [1, 0]],
[[7, 7, 6]],
[[7, 7, 6], [-1, -2, -3]],
grid,
)


def validate_grid(grid: list[list[int]]) -> None:
"""
Validate that the rows and columns of the grid is sorted in decreasing order.
>>> for grid in test_grids:
... validate_grid(grid)
"""
assert all(row == sorted(row, reverse=True) for row in grid)
assert all(list(col) == sorted(col, reverse=True) for col in zip(*grid))


def find_negative_index(array: list[int]) -> int:
"""
Find the smallest negative index

>>> find_negative_index([0,0,0,0])
4
>>> find_negative_index([4,3,2,-1])
3
>>> find_negative_index([1,0,-1,-10])
2
>>> find_negative_index([0,0,0,-1])
3
>>> find_negative_index([11,8,7,-3,-5,-9])
3
>>> find_negative_index([-1,-1,-2,-3])
0
>>> find_negative_index([5,1,0])
3
>>> find_negative_index([-5,-5,-5])
0
>>> find_negative_index([0])
1
>>> find_negative_index([])
0
"""
left = 0
right = len(array) - 1

# Edge cases such as no values or all numbers are negative.
if not array or array[0] < 0:
return 0

while right + 1 > left:
mid = (left + right) // 2
num = array[mid]

# Num must be negative and the index must be greater than or equal to 0.
if num < 0 and array[mid - 1] >= 0:
return mid

if num >= 0:
left = mid + 1
else:
right = mid - 1
# No negative numbers so return the last index of the array + 1 which is the length.
return len(array)


def count_negatives_binary_search(grid: list[list[int]]) -> int:
"""
An O(m logn) solution that uses binary search in order to find the boundary between
positive and negative numbers

>>> [count_negatives_binary_search(grid) for grid in test_grids]
[8, 0, 0, 3, 1498500]
"""
total = 0
bound = len(grid[0])

for i in range(len(grid)):
bound = find_negative_index(grid[i][:bound])
total += bound
return (len(grid) * len(grid[0])) - total


def count_negatives_brute_force(grid: list[list[int]]) -> int:
"""
This solution is O(n^2) because it iterates through every column and row.

>>> [count_negatives_brute_force(grid) for grid in test_grids]
[8, 0, 0, 3, 1498500]
"""
return len([number for row in grid for number in row if number < 0])


def count_negatives_brute_force_with_break(grid: list[list[int]]) -> int:
"""
Similar to the brute force solution above but uses break in order to reduce the
number of iterations.

>>> [count_negatives_brute_force_with_break(grid) for grid in test_grids]
[8, 0, 0, 3, 1498500]
"""
total = 0
for row in grid:
for i, number in enumerate(row):
if number < 0:
total += len(row) - i
break
return total


def benchmark() -> None:
"""Benchmark our functions next to each other"""
from timeit import timeit

print("Running benchmarks")
setup = (
"from __main__ import count_negatives_binary_search, "
"count_negatives_brute_force, count_negatives_brute_force_with_break, grid"
)
for func in (
"count_negatives_binary_search", # took 0.7727 seconds
"count_negatives_brute_force_with_break", # took 4.6505 seconds
"count_negatives_brute_force", # took 12.8160 seconds
):
time = timeit(f"{func}(grid=grid)", setup=setup, number=500)
print(f"{func}() took {time:0.4f} seconds")


if __name__ == "__main__":
import doctest

doctest.testmod()
benchmark()