Skip to content

Commit aff77c8

Browse files
authored
Merge pull request #172 from BrianLusina/feat/algorithms-top-k-elements
feat(algorithms, top k elements): smallest range covering elements from k lists
2 parents 414e19d + d8e1ec6 commit aff77c8

40 files changed

Lines changed: 466 additions & 0 deletions

File tree

DIRECTORY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@
312312
* [Test Find All Subsets](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/subsets/find_all_subsets/test_find_all_subsets.py)
313313
* Taxi Numbers
314314
* [Taxi Numbers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/taxi_numbers/taxi_numbers.py)
315+
* Top K Elements
316+
* Smallest Range Covering K Lists
317+
* [Test Smallest Range Covering Elements From K Lists](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/top_k_elements/smallest_range_covering_k_lists/test_smallest_range_covering_elements_from_k_lists.py)
315318
* Two Pointers
316319
* Array 3 Pointers
317320
* [Test Array 3 Pointers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/array_3_pointers/test_array_3_pointers.py)

algorithms/top_k_elements/__init__.py

Whitespace-only changes.

algorithms/top_k_elements/smallest_range_covering_k_lists/README.md

Lines changed: 295 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from collections import defaultdict
2+
from typing import List, Tuple, DefaultDict
3+
import heapq
4+
5+
6+
def smallest_range(nums: List[List[int]]) -> List[int]:
7+
# min heap that will store (value, list index, element index) for the smallest elements
8+
min_heap: List[Tuple[int, int, int]] = []
9+
heapq.heapify(min_heap)
10+
# Initialize the maximum value among the current elements in the heap
11+
max_value = float("-inf")
12+
# Initialize range boundaries with placeholders
13+
range_start = 0
14+
range_end = float("inf")
15+
16+
number_of_lists = len(nums)
17+
18+
# insert the first elements from each list into the min-heap and update max_value to be the maximum value seen so far
19+
for list_idx in range(number_of_lists):
20+
# (value, list index, index within the list)
21+
value = nums[list_idx][0]
22+
heapq.heappush(min_heap, (value, list_idx, 0))
23+
# Track the max value among the current elements
24+
max_value = max(max_value, value)
25+
26+
# Continue processing until we can't proceed further
27+
while len(min_heap) == number_of_lists:
28+
# Pop the smallest element from the heap (min_val from one of the lists)
29+
min_value, row, col = heapq.heappop(min_heap)
30+
31+
# Update the smallest range if the current range is smaller
32+
if max_value - min_value < range_end - range_start:
33+
range_start = min_value
34+
range_end = max_value
35+
36+
# If possible, add the next element from the same row to the heap
37+
if col + 1 < len(nums[row]):
38+
next_value = nums[row][col + 1]
39+
# Push the next element from the same list
40+
heapq.heappush(min_heap, (next_value, row, col + 1))
41+
# Update max_val if needed
42+
max_value = max(max_value, next_value)
43+
44+
# If any list runs out of elements, we can't form a complete range anymore
45+
46+
# Return the smallest range found
47+
return [range_start, range_end]
48+
49+
50+
def smallest_range_two_pointer(nums: List[List[int]]) -> List[int]:
51+
merged: List[Tuple[int, int]] = []
52+
53+
# merge all lists with their list index
54+
for list_idx, num_list in enumerate(nums):
55+
for num in num_list:
56+
merged.append((num, list_idx))
57+
58+
# sort the merged list
59+
merged.sort()
60+
61+
# Two pointers to track the smallest range
62+
freq: DefaultDict[int, int] = defaultdict(int)
63+
left, count = 0, 0
64+
range_start, range_end = 0, float("inf")
65+
66+
for right in range(len(merged)):
67+
val = merged[right][1]
68+
freq[val] += 1
69+
if freq[val] == 1:
70+
count += 1
71+
72+
# when all lists are represented, try to shrink the window
73+
while count == len(nums):
74+
current_range = merged[right][0] - merged[left][0]
75+
if current_range < range_end - range_start:
76+
range_start = merged[left][0]
77+
range_end = merged[right][0]
78+
79+
freq[merged[left][1]] -= 1
80+
if freq[merged[left][1]] == 0:
81+
count -= 1
82+
83+
left += 1
84+
85+
return [range_start, range_end]
86+
87+
88+
def smallest_range_brute_force(nums: List[List[int]]) -> List[int]:
89+
k = len(nums)
90+
# Stores the current index of each list
91+
indices = [0] * k
92+
# To track the smallest range
93+
range_list = [0, float("inf")]
94+
95+
while True:
96+
cur_min, cur_max = float("inf"), float("-inf")
97+
min_list_index = 0
98+
99+
# Find the current minimum and maximum values across the lists
100+
for i in range(k):
101+
current_element = nums[i][indices[i]]
102+
103+
# Update the current minimum
104+
if current_element < cur_min:
105+
cur_min = current_element
106+
min_list_index = i
107+
108+
# Update the current maximum
109+
if current_element > cur_max:
110+
cur_max = current_element
111+
112+
# Update the range if a smaller one is found
113+
if cur_max - cur_min < range_list[1] - range_list[0]:
114+
range_list[0] = cur_min
115+
range_list[1] = cur_max
116+
117+
# Move to the next element in the list that had the minimum value
118+
indices[min_list_index] += 1
119+
if indices[min_list_index] == len(nums[min_list_index]):
120+
break
121+
122+
return range_list
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)