Skip to content

Commit 77f736f

Browse files
committed
9/3 to 9/4: Heaps problems
- k_closest_stars.py - k_largest_in_heap.py - online_median.py - sort_almost_sorted_array.py - sort_increasing_decreasing_array.py - sorted_arrays_merge.py
1 parent a98cb7a commit 77f736f

File tree

6 files changed

+214
-14
lines changed

6 files changed

+214
-14
lines changed

elements-of-programming-interviews/python/k_closest_stars.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import heapq
2+
import collections
13
import functools
24
import math
35
from typing import Iterator, List
@@ -28,8 +30,17 @@ def __eq__(self, rhs):
2830

2931

3032
def find_closest_k_stars(stars: Iterator[Star], k: int) -> List[Star]:
31-
# TODO - you fill in here.
32-
return []
33+
"""
34+
Could we just create heap, push stars in based on distance to earth, and evict if we store more than k?
35+
O(n * log(k)) time, O(k) space.
36+
"""
37+
38+
heap = []
39+
for star in stars:
40+
heapq.heappush(heap, (-star.distance, star))
41+
if len(heap) > k:
42+
heapq.heappop(heap)
43+
return [heapq.heappop(heap)[1] for i in range(k)][::-1]
3344

3445

3546
def comp(expected_output, output):

elements-of-programming-interviews/python/k_largest_in_heap.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,36 @@
22

33
from test_framework import generic_test, test_utils
44

5+
"""
6+
- In max heap, root is largest, and they only get smaller.
7+
- Leaves of item at i are at 2**i, 2**i+1
8+
- If one child is greater than the other, we can't explore the lesser until we explore the best ones
9+
- Could we use a heap for this?
10+
- each time we evaluate a node, put it into set of largest, and heappush all children.
11+
- heappop to get next node.
12+
- Do this k many times; always ensures that of children, we get the biggest.
13+
"""
14+
15+
from collections import namedtuple
16+
from heapq import heappush, heappop
17+
18+
entry = namedtuple("entry", ["val", "idx"])
19+
520

621
def k_largest_in_binary_heap(A: List[int], k: int) -> List[int]:
7-
# TODO - you fill in here.
8-
return []
22+
if (not A) or (len(A) <= k):
23+
return A
24+
largest = []
25+
heap = [entry(-A[0], 0)]
26+
while len(largest) < k:
27+
curr = heappop(heap)
28+
largest.append(-curr.val)
29+
left_idx, right_idx = 2*curr.idx+1, (2*curr.idx)+2
30+
if left_idx < len(A):
31+
heappush(heap, entry(-A[left_idx], left_idx))
32+
if right_idx < len(A):
33+
heappush(heap, entry(-A[right_idx], right_idx))
34+
return largest
935

1036

1137
if __name__ == '__main__':

elements-of-programming-interviews/python/online_median.py

+59-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,66 @@
22

33
from test_framework import generic_test
44

5+
"""
6+
1,0,3,5,2,0,1
7+
1,0.5,1,2,2,1.5,1
58
6-
def online_median(sequence: Iterator[int]) -> List[float]:
7-
# TODO - you fill in here.
8-
return []
9+
1 -> 1
10+
0,| 1 -> .5
11+
0,|1|,3 -> 1
12+
0,1,| 3,5 -> 2
13+
0,1,|2|,3,5 -> 2
14+
0,0,1, | 2,3,5 -> 1.5
15+
0,0,1,|1|,2,3,5 -> 1
16+
17+
if odd, middle element
18+
if even, average of two innermost elements.
19+
20+
brute force: insert, sort, calculate (nlogn)
21+
22+
if we kept the "smallest of the larger half" and "largest of the smaller half" elements,
23+
then we can just average them
24+
- can probably look at size of greatests and leasts to determine if odd number or not
25+
26+
27+
"""
28+
from heapq import heappush, heappop
29+
30+
31+
def online_median(arr: Iterator[int]) -> List[float]:
32+
medians = []
33+
try:
34+
first = next(arr)
35+
medians.append(first)
36+
second = next(arr)
37+
38+
left = [-first if first < second else -second]
39+
right = [first if first > second else second]
40+
medians.append((-left[0] + right[0])/2)
41+
except StopIteration:
42+
return medians
43+
44+
for val in arr:
45+
# ensure every element in left is less than smallest
46+
# in right
47+
if val < right[0]:
48+
heappush(left, -val)
49+
else:
50+
heappush(right, val)
51+
52+
# Ensure balance via shifting elements from one
53+
# heap to another (in order) if needed
54+
while len(left) - len(right) > 1:
55+
heappush(right, -heappop(left))
56+
while len(right) - len(left) > 1:
57+
heappush(left, -heappop(right))
58+
59+
# Compute median depending on even/odd number of elements
60+
if len(right) == len(left):
61+
medians.append((-left[0] + right[0])/2)
62+
else:
63+
medians.append(-left[0] if len(left) > len(right) else right[0])
64+
return medians
965

1066

1167
def online_median_wrapper(sequence):

elements-of-programming-interviews/python/sort_almost_sorted_array.py

+39-2
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,55 @@
22

33
from test_framework import generic_test
44

5+
"""
6+
ex: k=17, [1, -11, 2, -2, -19, 4, -9, 1, 10, -12, 6, -19, 9, -5, 0, 5, -4, 13, 19, 19, 11]
7+
return [-19, -19, -12, -11, -9, -5, -4, -2, 0, 1, 1, 2, 4, 5, 6, 9, 10, 11, 13, 19, 19]
8+
9+
- brute force is just sorting the array
10+
- if k is 0, array is already sorted
11+
- if k is 1, then between arr[i-1], arr[i], and arr[i+1] there is a minimal element.
12+
- the smallest element of the array is within k of 0.
13+
- the second smallest element is within k of 1.
14+
- if k is greater than or equal to array len, no better than brute force
15+
16+
"""
17+
from heapq import heappush, heappop
18+
519

620
def sort_approximately_sorted_array(sequence: Iterator[int],
721
k: int) -> List[int]:
8-
# TODO - you fill in here.
9-
return []
22+
heap = []
23+
ans = []
24+
i = 0
25+
while i <= k:
26+
try:
27+
heappush(heap, next(sequence))
28+
except StopIteration:
29+
pass
30+
i += 1
31+
while heap:
32+
ans.append(heappop(heap))
33+
try:
34+
heappush(heap, next(sequence))
35+
except StopIteration:
36+
continue
37+
return ans
1038

1139

1240
def sort_approximately_sorted_array_wrapper(sequence, k):
1341
return sort_approximately_sorted_array(iter(sequence), k)
1442

1543

1644
if __name__ == '__main__':
45+
cases = [
46+
([1, -11, 2, -2, -19, 4, -9, 1, 10, -12, 6, -19, 9, -5, 0, 5, -4, 13, 19, 19, 11], 17),
47+
([1, 2, 3, 4, 5, 6, 7], 0),
48+
([2, 1, 4, 3, 7, 6, 9, 8], 1),
49+
]
50+
for case, k in cases:
51+
expected = sorted(case)
52+
actual = sort_approximately_sorted_array_wrapper(case, k)
53+
assert actual == expected, f"\n{k}:{case}\n{actual} != {expected}"
1754
exit(
1855
generic_test.generic_test_main(
1956
'sort_almost_sorted_array.py', 'sort_almost_sorted_array.tsv',

elements-of-programming-interviews/python/sort_increasing_decreasing_array.py

+61-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,71 @@
22

33
from test_framework import generic_test
44

5+
"""
6+
Smallest case would be like:
7+
k = 3: 0 1 0 | 1 0 | 1 0
58
6-
def sort_k_increasing_decreasing_array(A: List[int]) -> List[int]:
7-
# TODO - you fill in here.
8-
return []
9+
brute force sort is nlogn, can ideally do better
10+
vals could be pos, neg, or zero
11+
12+
"""
13+
import heapq
14+
import collections
15+
16+
subarray = collections.namedtuple("subarray", ["val", "left", "right", "increasing"])
17+
18+
19+
def get_subarrays(arr):
20+
if not arr or (len(arr) == 1):
21+
return [arr]
22+
subarrays = []
23+
curr = []
24+
increasing = arr[0] < arr[1]
25+
i = 0
26+
while i < len(arr)-1:
27+
if ((arr[i] < arr[i+1]) and not increasing) or ((arr[i] > arr[i+1]) and increasing):
28+
subarrays.append(curr if increasing else curr[::-1])
29+
curr = []
30+
increasing = not increasing
31+
curr.append(arr[i])
32+
i += 1
33+
34+
if not curr:
35+
subarrays.append([arr[i]])
36+
else:
37+
curr.append(arr[i])
38+
subarrays.append(curr if increasing else curr[::-1])
39+
return subarrays
40+
41+
42+
def sort_k_increasing_decreasing_array(array: List[int]) -> List[int]:
43+
subarrs = get_subarrays(array)
44+
heap = [(arr[0], 0, i) for i, arr in enumerate(subarrs)]
45+
heapq.heapify(heap)
46+
merged = []
47+
while heap:
48+
val, idx, arr_id = heapq.heappop(heap)
49+
merged.append(val)
50+
new_idx = idx+1
51+
parent_arr = subarrs[arr_id]
52+
if new_idx < len(parent_arr):
53+
heapq.heappush(heap, (parent_arr[new_idx], new_idx, arr_id))
54+
55+
return merged
956

1057

1158
if __name__ == '__main__':
59+
cases = [
60+
# [1, 2, 3, 4, 5, 4, 3, 2, 1, 2, 3, 4, 5],
61+
# [1, 2, 3, 2, 1, 4, 5, 10, 9, 4, 4, 1, -1],
62+
# [1, 2, 4, 4, 5, 1, 2, 3, 4, 5, -10, 10, -10],
63+
[2147483647, 8, 4, 2, 1, 0, -1, -2147483648]
64+
]
65+
for case in cases:
66+
# print(get_subarrays(case))
67+
expected = sorted(case)
68+
actual = sort_k_increasing_decreasing_array(case)
69+
assert actual == expected, f"\ncase: {case}\n{actual} != {expected}"
1270
exit(
1371
generic_test.generic_test_main('sort_increasing_decreasing_array.py',
1472
'sort_increasing_decreasing_array.tsv',

elements-of-programming-interviews/python/sorted_arrays_merge.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@
22

33
from test_framework import generic_test
44

5+
import heapq
6+
57

68
def merge_sorted_arrays(sorted_arrays: List[List[int]]) -> List[int]:
7-
# TODO - you fill in here.
8-
return []
9+
heap = [(arr[0], 0, i) for i, arr in enumerate(sorted_arrays) if arr]
10+
heapq.heapify(heap)
11+
merged = []
12+
while heap:
13+
val, idx, arr_id = heapq.heappop(heap)
14+
merged.append(val)
15+
new_idx = idx+1
16+
parent_arr = sorted_arrays[arr_id]
17+
if new_idx < len(parent_arr):
18+
heapq.heappush(heap, (parent_arr[new_idx], new_idx, arr_id))
19+
20+
return merged
921

1022

1123
if __name__ == '__main__':

0 commit comments

Comments
 (0)