|
| 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 |
0 commit comments