Back to blog
Dec 16, 2024
4 min read

LeetCode 632: Smallest Range Covering Elements from K Lists

Leetcode 632: Smallest Range Covering Elements from K Lists solution in Python

Problem Description

LeetCode Problem 632

You have k lists of sorted integers in non-decreasing order. Find the smallest range that includes at least one number from each of the k lists.

We define the range [a, b] is smaller than range [c, d] if b - a < d - c or a < c if b - a == d - c.

 

Example 1:

Input: nums = [[4,10,15,24,26],[0,9,12,20],[5,18,22,30]] Output: [20,24] Explanation: List 1: [4, 10, 15, 24,26], 24 is in range [20,24]. List 2: [0, 9, 12, 20], 20 is in range [20,24]. List 3: [5, 18, 22, 30], 22 is in range [20,24].

Example 2:

Input: nums = [[1,2,3],[1,2,3],[1,2,3]] Output: [1,1]

 

Constraints:

  • nums.length == k
  • 1 <= k <= 3500
  • 1 <= nums[i].length <= 50
  • -105 <= nums[i][j] <= 105
  • nums[i] is sorted in non-decreasing order.

Difficulty: Hard

Tags: array, hash table, greedy, sliding window, sorting, heap (priority queue)

Rating: 97.73%

Solution Approach

The problem can be solved efficiently using a min-heap data structure and a sliding window technique. Here’s how the algorithm works:

  1. Initialize a min-heap with the first element from each list, keeping track of:

    • The value
    • Which list it came from (list index)
    • Position in that list (element index)
  2. Keep track of the maximum value among current elements in consideration

  3. While the heap contains an element from each list:

    • Pop the minimum element
    • Compare current range (max - min) with best range found so far
    • Add the next element from the same list (if available)
  4. Return the smallest range found

Implementation in Python

class Solution:
    def smallestRange(self, nums: List[List[int]]) -> List[int]:
        # Initialize minheap with first element from each list
        heap = []
        max_val = float('-inf')
        for i, lst in enumerate(nums):
            if lst:
                heappush(heap, (lst[0], i, 0))
                max_val = max(max_val, lst[0])
        
        # Initialize result range
        s, e = 0, float('inf')
        
        # Until any list is exhausted
        while len(heap) == len(nums):
            min_val, list_idx, elem_idx = heappop(heap)
            # Update range if current range is smaller
            if max_val - min_val < e - s:
                s = min_val
                e = max_val
            elif max_val - min_val == e - s and min_val < s:
                s = min_val
                e = max_val
            
            # If there are more elements in current list, add next element
            if elem_idx + 1 < len(nums[list_idx]):
                next_val = nums[list_idx][elem_idx + 1]
                heappush(heap, (next_val, list_idx, elem_idx + 1))
                max_val = max(max_val, next_val)
        
        return [s, e]

Detailed Walkthrough

Let’s walk through Example 1 step by step:

Input: nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]

  1. Initial State:

    • Heap: [(0,1,0), (4,0,0), (5,2,0)]
    • max_val: 5
    • Current best range: [0, ∞]
  2. First Iteration:

    • Pop (0,1,0) from heap
    • Add (9,1,1) to heap
    • max_val: 9
    • Range: [0,9]
  3. Second Iteration:

    • Pop (4,0,0) from heap
    • Add (10,0,1) to heap
    • max_val: 10
    • Range: [0,10]
  4. Continue iterations…

  5. Final State:

    • After several iterations, we find [20,24] is the smallest range
    • It contains:
      • 24 from first list
      • 20 from second list
      • 22 from third list

Complexity Analysis

  • Time Complexity: O(nlog(k))O(n * log(k))

    • n is the total number of elements across all lists
    • k is the number of lists
    • Each element is pushed/popped from heap once
    • Heap operations cost O(log(k))O(log(k))
  • Space Complexity: O(k)O(k)

    • The heap stores at most k elements at any time
    • k is the number of lists

Key Insights

  1. Why use a min-heap?

    • Efficiently tracks minimum value across k lists
    • Maintains sorted order while processing elements
  2. Why track max_val separately?

    • Avoids need to search through heap for maximum
    • Constant time access to range boundaries
  3. When to update the range?

    • When current range is smaller than best found
    • When ranges are equal but current starts earlier