Skip to content

Commit 0c625a7

Browse files
Add binary search and stack-related algorithms
Introduce binary search-based solutions for common problems like searching in arrays, 2D matrices, and optimization problems. Additionally, implement stack-based solutions for tasks such as daily temperature calculation, largest rectangle in a histogram, and car fleet determination, each supplemented with unit tests.
1 parent ab1796b commit 0c625a7

File tree

8 files changed

+234
-0
lines changed

8 files changed

+234
-0
lines changed

Arrays & Hashing/top_k_frequent_elements.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
from typing import List
66
from collections import Counter
77

8+
# The nlargest function allows you to find the k largest elements
9+
# from an iterable based on a specified key function.
10+
#
11+
# def top_k_frequent(nums: List[int], k: int) -> List[int]:
12+
# counts = Counter(nums)
13+
#
14+
# # Use heapq.nlargest to get the k elements with the highest counts
15+
# return heapq.nlargest(k, counts.keys(), key=counts.get)
16+
817
def top_k_frequent(nums: List[int], k: int) -> List[int]:
918
n = len(nums)
1019

Binary Search/__init__.py

Whitespace-only changes.

Binary Search/binary_search.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# https://neetcode.io/problems/binary-search
2+
3+
import unittest
4+
from typing import List
5+
6+
# Python has a builtin binary search function: 'bisect_left'.
7+
# The bisect_left function from Python's bisect module is used
8+
# to find the insertion point for an element in a sorted list to
9+
# maintain sorted order.
10+
#
11+
# from bisect import bisect_left
12+
# def binary_search(nums: List[int], target: int) -> int:
13+
# # Find the index where target should be inserted
14+
# index = bisect_left(nums, target)
15+
#
16+
# # Check if the target is actually present at the found index
17+
# if index != len(nums) and nums[index] == target:
18+
# return index
19+
#
20+
# # If target is not present, return -1
21+
# return -1
22+
23+
def binary_search(nums: List[int], target: int) -> int:
24+
n = len(nums)
25+
26+
left = 0
27+
right = n - 1
28+
29+
while left <= right:
30+
mid = (left + right) // 2
31+
32+
if nums[mid] == target:
33+
return mid
34+
35+
if nums[mid] < target:
36+
left = mid + 1
37+
else:
38+
right = mid - 1
39+
40+
return -1
41+
42+
class Test(unittest.TestCase):
43+
def test_binary_search(self):
44+
self.assertEqual(binary_search([-1, 0, 2, 4, 6, 8], 4), 3)
45+
self.assertEqual(binary_search([-1, 0, 2, 4, 6, 8], 3), -1)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# https://neetcode.io/problems/eating-bananas
2+
import math
3+
import unittest
4+
from typing import List
5+
6+
def min_eating_speed(piles: List[int], hours: int) -> int:
7+
piles.sort()
8+
9+
left = 1
10+
right = max(piles)
11+
result = right
12+
13+
while left < right:
14+
k = (left + right) // 2
15+
16+
total_time = 0
17+
for pile in piles:
18+
total_time += math.ceil(float(pile) / k)
19+
20+
if total_time <= hours:
21+
result = k
22+
right = k - 1
23+
else:
24+
left = k + 1
25+
26+
return result
27+
28+
class Test(unittest.TestCase):
29+
def test_min_eating_speed(self):
30+
self.assertEqual(min_eating_speed([1, 4, 3, 2], 9), 2)
31+
self.assertEqual(min_eating_speed([25, 10, 23, 4], 4), 25)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# https://neetcode.io/problems/search-2d-matrix
2+
3+
import unittest
4+
from typing import List
5+
6+
# 'bisect_left' can be used here, but requires flattening the matrix first
7+
# from bisect import bisect_left
8+
#
9+
# def search_matrix(matrix: List[List[int]], target: int) -> bool:
10+
# if not matrix or not matrix[0]:
11+
# return False
12+
#
13+
# # Flatten the matrix
14+
# flat_list = [element for row in matrix for element in row]
15+
#
16+
# # Use bisect_left to find the insertion point
17+
# index = bisect_left(flat_list, target)
18+
#
19+
# # Check if the target is actually at the found index
20+
# return index < len(flat_list) and flat_list[index] == target
21+
22+
def search_matrix(matrix: List[List[int]], target: int) -> bool:
23+
n = len(matrix)
24+
m = len(matrix[0])
25+
26+
rows = n
27+
cols = m
28+
29+
left = 0
30+
right = (rows * cols) - 1
31+
32+
while left <= right:
33+
mid = (left + right) // 2
34+
35+
row = mid // rows
36+
col = mid // cols
37+
38+
if matrix[row][col] == target:
39+
return True
40+
41+
if matrix[row][col] < target:
42+
left = mid + 1
43+
else:
44+
right = mid - 1
45+
46+
return False
47+
48+
class Test(unittest.TestCase):
49+
def test_search_matrix(self):
50+
self.assertTrue(search_matrix([[1, 2, 4, 8],[10, 11, 12, 13],[14, 20, 30, 40]], 10))
51+
self.assertFalse(search_matrix([[1, 2, 4, 8],[10, 11, 12, 13],[14, 20, 30, 40]], 15))

Stack/car_fleet.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# https://neetcode.io/problems/car-fleet
2+
3+
import unittest
4+
from typing import List
5+
6+
def car_fleet(target: int, positions: List[int], speeds: List[int]) -> int:
7+
# Create a list of tuples where each tuple contains a car's position and speed.
8+
cars = [(position, speed) for position, speed in zip(positions, speeds)]
9+
10+
# Sort the cars by their starting positions in descending order.
11+
# This ensures that we process cars starting closest to the target first.
12+
# A car can only form a fleet with another car that is ahead of it,
13+
# so sorting by descending position helps in determining if a car will
14+
# catch up to another car in front of it.
15+
cars.sort(reverse=True)
16+
17+
# Initialize a stack to keep track of the time each car fleet takes to reach the target.
18+
stack = []
19+
20+
for position, speed in cars:
21+
# Calculate the time it takes for the current car to reach the target.
22+
time_to_target = (target - position) / speed
23+
# Push the time to the stack, representing a new fleet or an existing fleet's time.
24+
stack.append(time_to_target)
25+
26+
# Check if the current car can catch up to the fleet in front of it.
27+
# If the current car's time is less than or equal to the fleet's time in front,
28+
# it means it will merge into the same fleet, so we remove it from the stack.
29+
if len(stack) >= 2 and stack[-1] <= stack[-2]:
30+
stack.pop()
31+
32+
# The number of distinct times in the stack represents the number of car fleets.
33+
return len(stack)
34+
35+
class Test(unittest.TestCase):
36+
def test_car_fleet(self):
37+
self.assertEqual(car_fleet(10, [1, 4], [3, 2]), 1)
38+
self.assertEqual(car_fleet(10, [4, 1, 0, 7], [2, 2, 1, 1]), 3)

Stack/daily_temperatures.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# https://neetcode.io/problems/daily-temperatures
2+
3+
import unittest
4+
from typing import List
5+
6+
def daily_temperatures(temperatures: List[int]) -> List[int]:
7+
n = len(temperatures)
8+
result = [0] * n
9+
seen_temperatures = [] # (temperature, index)
10+
11+
for index, temperature in enumerate(temperatures):
12+
while seen_temperatures and temperature > seen_temperatures[-1][0]:
13+
seen_temperature, seen_index = seen_temperatures.pop()
14+
result[seen_index] = index - seen_index
15+
16+
seen_temperatures.append((temperature, index))
17+
18+
return result
19+
20+
class Test(unittest.TestCase):
21+
def test_daily_temperatures(self):
22+
self.assertEqual(daily_temperatures([30, 38, 30, 36, 35, 40, 28]), [1, 4, 1, 2, 1, 0, 0])
23+
self.assertEqual(daily_temperatures([22, 21, 20]), [0, 0, 0])
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# https://neetcode.io/problems/largest-rectangle-in-histogram
2+
3+
import unittest
4+
from typing import List
5+
6+
def largest_rectangle_area(heights: List[int]) -> int:
7+
num_bars = len(heights)
8+
stack = [] # This stack will store tuples of (start_index, bar_height)
9+
max_area = 0 # Variable to keep track of the maximum area found
10+
11+
for current_index, current_height in enumerate(heights):
12+
start_index = current_index # Start index for the current bar
13+
14+
# While stack is not empty and the current bar height is less
15+
# than the height of the bar at the top of the stack
16+
while stack and stack[-1][1] > current_height:
17+
popped_index, popped_height = stack.pop() # Pop the top of the stack
18+
19+
# Calculate the area with the popped height as the smallest height
20+
max_area = max(max_area, popped_height * (current_index - popped_index))
21+
22+
start_index = popped_index # Update start index to the popped index
23+
24+
# Push the current bar onto the stack with its start index
25+
stack.append((start_index, current_height))
26+
27+
# After processing all bars, compute area for remaining bars in the stack
28+
for start_index, bar_height in stack:
29+
# Calculate area with the remaining heights in the stack
30+
max_area = max(max_area, bar_height * (num_bars - start_index))
31+
32+
return max_area
33+
34+
class Test(unittest.TestCase):
35+
def test_largest_rectangle_area(self):
36+
self.assertEqual(largest_rectangle_area([7, 1, 7, 2, 2, 4]), 8)
37+
self.assertEqual(largest_rectangle_area([1, 3, 7]), 7)

0 commit comments

Comments
 (0)