[LeetCode] 632. Smallest Range Covering Elements from K Lists 覆蓋K個列表元素的最小區間


 

You have k lists of sorted integers in ascending 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:[[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].

 

Note:

  1. The given list may contain duplicates, so ascending order means >= here.
  2. 1 <= k <= 3500
  3. -105 <= value of elements <= 105.
  4. For Java users, please note that the input type has been changed to List<List<Integer>>. And after you reset the code template, you'll see this point.

 

這道題給了我們一些數組,都是排好序的,讓求一個最小的范圍,使得這個范圍內至少會包括每個數組中的一個數字。雖然每個數組都是有序的,但是考慮到他們之間的數字差距可能很大,所以最好還是合並成一個數組統一處理比較好,但是合並成一個大數組還需要保留其原屬數組的序號,所以大數組中存pair對,同時保存數字和原數組的序號。然后重新按照數字大小進行排序,這樣問題實際上就轉換成了求一個最小窗口,使其能夠同時包括所有數組中的至少一個數字。這不就變成了那道 Minimum Window Substring。所以說啊,這些題目都是換湯不換葯的,總能變成我們見過的類型。這里用兩個指針 left 和 right 來確定滑動窗口的范圍,還要用一個 HashMap 來建立每個數組與其數組中數字出現的個數之間的映射,變量 cnt 表示當前窗口中的數字覆蓋了幾個數組,diff 為窗口的大小,讓 right 向右滑動,然后判斷如果 right 指向的數字所在數組沒有被覆蓋到,cnt 自增1,然后 HashMap 中對應的數組出現次數自增1,然后循環判斷如果 cnt 此時為k(數組的個數)且 left 不大於 right,那么用當前窗口的范圍來更新結果,然后此時想縮小窗口,通過將 left 向右移,移動之前需要減小 HashMap 中的映射值,因為去除了數字,如果此時映射值為0了,說明有個數組無法覆蓋到了,cnt 就要自減1。這樣遍歷后就能得到最小的范圍了,參見代碼如下:

 

解法一:

class Solution {
public:
    vector<int> smallestRange(vector<vector<int>>& nums) {
        vector<int> res;
        vector<pair<int, int>> v;
        unordered_map<int, int> m;
        for (int i = 0; i < nums.size(); ++i) {
            for (int num : nums[i]) {
                v.push_back({num, i});
            }
        }
        sort(v.begin(), v.end());
        int left = 0, n = v.size(), k = nums.size(), cnt = 0, diff = INT_MAX;
        for (int right = 0; right < n; ++right) {
            if (m[v[right].second] == 0) ++cnt;
            ++m[v[right].second];
            while (cnt == k && left <= right) {
                if (diff > v[right].first - v[left].first) {
                    diff = v[right].first - v[left].first;
                    res = {v[left].first, v[right].first};
                } 
                if (--m[v[left].second] == 0) --cnt;
                ++left;
            }
        }
        return res;
    }
};

 

這道題還有一種使用 priority_queue 來做的,優先隊列默認情況是最大堆,但是這道題我們需要使用最小堆,重新寫一下 comparator 就行了。解題的主要思路很上面的解法很相似,只是具體的數據結構的使用上略有不同,這 curMax 表示當前遇到的最大數字,用一個 idx 數組表示每個 list 中遍歷到的位置,然后優先隊列里面放一個pair,是數字和其所屬list組成的對兒。遍歷所有的list,將每個 list 的首元素和該 list 序號組成 pair 放入隊列中,然后 idx 數組中每個位置都賦值為1,因為0的位置已經放入隊列了,所以指針向后移一個位置,還要更新當前最大值 curMax。此時 queue 中是每個 list 各有一個數字,由於是最小堆,所以最小的數字就在隊首,再加上最大值 curMax,就可以初始化結果 res 了。然后進行循環,注意這里循環的條件不是隊列不為空,而是當某個 list 的數字遍歷完了就結束循環,因為范圍要 cover 每個 list 至少一個數字。所以 while 循環條件即是隊首數字所在的 list 的遍歷位置小於該 list 的總個數,在循環中,取出隊首數字所在的 list 序號t,然后將該 list 中下一個位置的數字和該 list 序號t組成 pair,加入隊列中,然后用這個數字更新 curMax,同時 idx 中t對應的位置也自增1。現在來更新結果 res,如果結果 res 中兩數之差大於 curMax 和隊首數字之差,則更新結果 res,參見代碼如下:

 

解法二:

class Solution {
public:
    vector<int> smallestRange(vector<vector<int>>& nums) {
        int curMax = INT_MIN, n = nums.size();
        vector<int> idx(n, 0);
        auto cmp = [](pair<int, int>& a, pair<int, int>& b) {return a.first > b.first;};
        priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp) > q(cmp);
        for (int i = 0; i < n; ++i) {
            q.push({nums[i][0], i});
            idx[i] = 1;
            curMax = max(curMax, nums[i][0]);
        }
        vector<int> res{q.top().first, curMax};
        while (idx[q.top().second] < nums[q.top().second].size()) {
            int t = q.top().second; q.pop();
            q.push({nums[t][idx[t]], t});
            curMax = max(curMax, nums[t][idx[t]]);
            ++idx[t];
            if (res[1] - res[0] > curMax - q.top().first) {
                res = {q.top().first, curMax};
            }
        }
        return res;
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/632

 

類似題目:

Minimum Window Substring

 

參考資料:

https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/

https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/discuss/104893/Java-Code-using-PriorityQueue.-similar-to-merge-k-array

https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/discuss/104886/Clean-C%2B%2B-priority_queue-solution-using-iterators

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM