Smallest Number Range (Hard)
Dry-run templates
This is the implementation of the solution provided for the problem posed in the Smallest Number Range (Hard) lesson:
from heapq import *import mathdef find_smallest_range(lists):minHeap = []rangeStart, rangeEnd = 0, math.infcurrentMaxNumber = -math.inf# put the 1st element of each array in the min heapfor arr in lists:heappush(minHeap, (arr[0], 0, arr))currentMaxNumber = max(currentMaxNumber, arr[0])# take the smallest(top) element form the min heap, if it gives us smaller range, update the ranges# if the array of the top element has more elements, insert the next element in the heapwhile len(minHeap) == len(lists):num, i, arr = heappop(minHeap)if rangeEnd - rangeStart > currentMaxNumber - num:rangeStart = numrangeEnd = currentMaxNumberif len(arr) > i+1:# insert the next element in the heapheappush(minHeap, (arr[i+1], i+1, arr))currentMaxNumber = max(currentMaxNumber, arr[i+1])return [rangeStart, rangeEnd]def main():print("Smallest range is: " +str(find_smallest_range([[1, 5, 8], [4, 12], [7, 8, 10]])))main()
Students may be encouraged to run through the provided solution with the following sample inputs:
Sample Input #1
lists: [[1, 2], [3,4], [5,6]]
Iteration No. | Line No. | minHeap | currentMaxNumber | num, i, arr | rangeStart -- rangeEnd |
- | 6-8 | [] | -infinity | - | 0 - infinity |
1 | 11-13 | [(1, 0, [1, 2])] | 1 | - | 0 - infinity |
2 | 11-13 | [(1, 0, [1, 2]), (3, 0, [3, 4])] | 3 | - | 0 - infinity |
3 | 11-13 | [(1, 0, [1, 2]), (3, 0, [3, 4]), (5, 0, [5, 6])] | 5 | - | 0 - infinity |
1 | 18 | [(3, 0, [3, 4]), (5, 0, [5, 6])] | 5 | 1,0,[1, 2] | 0 - infinity |
1 | 19-21 | [(3, 0, [3, 4]), (5, 0, [5, 6])] | 5 | 1,0,[1, 2] | 1- 5 |
1 | 23-26 | [(2, 1, [1, 2]), (5, 0, [5, 6]), (3, 0, [3, 4])] | 5 | 1,0,[1, 2] | 1- 5 |
2 | 18 | [(5, 0, [5, 6]), (3, 0, [3, 4])] | 5 | 2,1,[1, 2] | 1 - 5 |
2 | 19-21 | [(5, 0, [5, 6]), (3, 0, [3, 4])] | 5 | 2,1,[1, 2] | 2 - 5 |
Sample Input #2
lists: [[10, 20, 30], [1, 2, 3], [7, 8, 19]]
Iteration No. | Line No. | minHeap | currentMaxNumber | num, i, arr | rangeStart -- rangeEnd |
- | 6-8 | [] | -infinity | - | 0 - infinity |
1 | 11-13 | [(10, 0, [10, 20, 30])] | 10 | - | 0 - infinity |
2 | 11-13 | [(1, 0, [1, 2, 3]), (10, 0, [10, 20, 30])] | 10 | - | 0 - infinity |
3 | 11-13 | [(1, 0, [1, 2, 3]), (10, 0, [10, 20, 30]), (7, 0, [7, 8, 19])] | 10 | - | 0 - infinity |
1 | 18 | [(10, 0, [10, 20, 30]), (7, 0, [7, 8, 19])] | 10 | 1,0,[1, 2, 3] | 0 - infinity |
1 | 19-21 | [(10, 0, [10, 20, 30]), (7, 0, [7, 8, 19])] | 10 | 1,0,[1, 2, 3] | 1- 10 |
1 | 23-26 | [(2, 1, [1, 2, 3]), (10, 0, [10, 20, 30]), (7, 0, [7, 8, 19])] | 10 | 1,0,[1, 2, 3] | 1- 10 |
2 | 18 | [(10, 0, [10, 20, 30]), (7, 0, [7, 8, 19])] | 10 | 2,1,[1, 2, 3] | 1 - 10 |
2 | 19-21 | [(10, 0, [10, 20, 30]), (7, 0, [7, 8, 19])] | 10 | 2,1,[1, 2, 3] | 2 - 10 |
2 | 23-26 | [(3, 2, [1, 2, 3]), (10, 0, [10, 20, 30]), (7, 0, [7, 8, 19])] | 10 | 2,1,[1, 2, 3] | 2 - 10 |
3 | 18 | [(7, 0, [7, 8, 19]), (10, 0, [10, 20, 30])] | 10 | 3,2,[1, 2, 3] | 2-10 |
3 | 19-21 | [(7, 0, [7, 8, 19]), (10, 0, [10, 20, 30])] | 10 | 3,2,[1, 2, 3] | 3-10 |
Step-by-step solution construction
Initially, we need to add the first element of each list to the minHeap
. At the same time, we update currentMaxNumber
with every insertion, to keep track of the largest number encountered so far.
In the following code, on line 14, we take the first element of the current list arr
and insert it into minHeap
along with its index, 0
, in arr
, and the current list arr
itself.
On line 16, we update currentMaxNumber
if the newly inserted number is greater than the currentMaxNumber
.
from heapq import *import mathdef find_smallest_range(lists):print("Input lists:", lists)minHeap = []rangeStart, rangeEnd = 0, math.infcurrentMaxNumber = -math.infprint("Inserting 1st element of each array in the minHeap")# put the 1st element of each array in the min heapfor arr in lists:heappush(minHeap, (arr[0], 0, arr))print("minHeap: " + str(minHeap))currentMaxNumber = max(currentMaxNumber, arr[0])print("currentMaxNumber: " + str(currentMaxNumber))return [rangeStart, rangeEnd]def main():lists = [[[2, 3], [4,5], [6,7]],[[1, 2, 30], [9, 10,15], [7, 8, 10]]]for i in range(len(lists)):[rangeStart,rangeEnd] = find_smallest_range(lists[i])print("Smallest range in " + str(lists[i]) + " is: " + str(rangeStart) + "-" + str(rangeEnd))print("-"*100)main()
Let’s add a loop that will continue to iterate as long as there are as many elements in the heap as there are input lists.
Each iteration of the loop will pop
the minimum element from the minHeap
(line 28) and push
the next element from the list that contains the element just popped (line 35).
For example, if the second element belonging to the third list was just popped, then if the third list has a third element, that element is pushed on to the heap.
This pairing of pop
and push
ensures that the heap always contains one element each from each of the k
input lists.
We also update currentMaxNumber
if the newly inserted element is greater than the current value of currentMaxNumber
(line 36).
We are not yet updating
rangeStart
andrangeEnd
.
from heapq import *import mathdef find_smallest_range(lists):print("Input lists:", lists)minHeap = []rangeStart, rangeEnd = 0, math.infcurrentMaxNumber = -math.infprint("Inserting 1st element of each array in the minHeap")# put the 1st element of each array in the min heapfor arr in lists:heappush(minHeap, (arr[0], 0, arr))print("minHeap: " + str(minHeap))currentMaxNumber = max(currentMaxNumber, arr[0])print("currentMaxNumber: " + str(currentMaxNumber))print("Set-up complete. About to start pop-and-push cycle:\n")iteration = 0;# pop the smallest(top) element from the min heap# if there are elements left in the array to which the top element belongs,# push the next element from that array on to the heapwhile len(minHeap) == len(lists):print("\nIteration: "+str(iteration+1))iteration +=1print("\tminHeap: " + str(minHeap))num, i, arr = heappop(minHeap)print("\tnum, i, arr: "+str(num)+", "+str(i)+", "+str(arr))print("\tCurrent Range: " + str(rangeStart) + "-" + str(rangeEnd))print("\tcurrentMaxNumber: "+str(currentMaxNumber))if len(arr) > i+1:# insert the next element in the heapheappush(minHeap, (arr[i+1], i+1, arr))currentMaxNumber = max(currentMaxNumber, arr[i+1])return [rangeStart, rangeEnd]def main():lists = [[[2, 3], [4,5], [6,7]],[[1, 2, 30], [9, 10,15], [7, 8, 10]]]for i in range(len(lists)):[rangeStart,rangeEnd] = find_smallest_range(lists[i])print("Smallest range in " + str(lists[i]) + " is: " + str(rangeStart) + "-" + str(rangeEnd))print("-"*100)main()
As a final step, we need to add conditions to update the range if the popped element and the currentMaxNumber
make a smaller range than rangeStart
- rangeEnd
.
In the following code, we apply this condition on lines 32 - 35.
The solution works because at every stage, we make sure that the
minHeap
contains one element each from each of thek
input lists. The popped element, in each iteration, is the minimum ofk
values being considered, hence, it’s a valid candidate to become the lower boundary of the smallest range. Similarly, as we push the next larger number on to the heap, we check if it is greater than the largest number encountered so far. These two factors ensure the correctness of the condition on line 32.
from heapq import *import mathdef find_smallest_range(lists):print("Input lists:", lists)minHeap = []rangeStart, rangeEnd = 0, math.infcurrentMaxNumber = -math.infprint("Inserting 1st element of each array in the minHeap")# put the 1st element of each array in the min heapfor arr in lists:heappush(minHeap, (arr[0], 0, arr))print("minHeap: " + str(minHeap))currentMaxNumber = max(currentMaxNumber, arr[0])print("currentMaxNumber: " + str(currentMaxNumber))print("Set-up complete. About to start pop-and-push cycle:\n")iteration = 0;# take the smallest(top) element from the min heap,# if it gives us a smaller range, update the range# if there are elements left in the array to which the top element belongs,# push the next element from that array on to the heapwhile len(minHeap) == len(lists):print("\nIteration: "+str(iteration+1))iteration +=1print("\tminHeap: " + str(minHeap))num, i, arr = heappop(minHeap)print("\tnum, i, arr: "+str(num)+", "+str(i)+", "+str(arr))print("\tCurrent Range: " + str(rangeStart) + "-" + str(rangeEnd))print("\tcurrentMaxNumber: "+str(currentMaxNumber))if rangeEnd - rangeStart > currentMaxNumber - num:rangeStart = numrangeEnd = currentMaxNumberprint("\t\tRange updated: "+str(rangeStart)+"-"+str(rangeEnd))if len(arr) > i+1:# insert the next element in the heapheappush(minHeap, (arr[i+1], i+1, arr))currentMaxNumber = max(currentMaxNumber, arr[i+1])return [rangeStart, rangeEnd]def main():lists = [[[2, 3], [4,5], [6,7]],[[1, 2, 30], [9, 10,15], [7, 8, 10]]]for i in range(len(lists)):[rangeStart,rangeEnd] = find_smallest_range(lists[i])print("Smallest range in " + str(lists[i]) + " is: " + str(rangeStart) + "-" + str(rangeEnd))print("-"*100)main()