题目链接
分析
贪心 + 最小堆
由于题目所求的区间需要至少在每个区间包含一个元素,所以我们使用贪心的思想。每次计算时,仅取每个列表中最小的元素,构成一个合法的区间。那么这个区间的左右端点分别是我们取出元素的最小值和最大值。
然后我们将最小值踢出该区间,使用最小值所在的列表的下一个元素构成新的合法区间。即枚举合法区间的左端点,计算每个合法区间的长度,枚举所有区间后最短的合法区间即为答案。
考虑示例 1
$\begin{matrix} \downarrow \\ [4,& 10, & 15, & 24, & 26] \\ [0, & 9, & 12, & 20] \\ [5, & 8, & 22, & 30] \end{matrix}$
这三个列表构成的最小的合法区间即是最左侧的一列构成的区间 $[0, 4]$ ,区间的左右端点即为这一列的最大最小值。
接下来,去掉这一列中的最小值 $0$,用 $0$ 所在列表的下一个值代替
$\begin{matrix} \downarrow \\ [4,& 10, & 15, & 24, & 26] \\ [9, & 12, & 20] \\ [5, & 8, & 22, & 30] \end{matrix}$
这三个列表所构成的最小的合法区间为 $[4, 9]$,左端点为这一列元素的最小值 $4$ ,右端点为这一列元素的最大值 $9$
接下来重复操作,去掉最小值 $4$,列表变为:
$\begin{array}{llll} \ \downarrow \\ [10, & 15, & 24, & 26] \\ [9, & 12, & 20] \\ [5, & 8, & 22, & 30] \end{array}$
重复该步骤进行计算即可
所以我们维护一个最小堆来计算区间的最小值,使用一个变量来记录区间中的最大值,每次循环时,维护合法区间的最短长度即可。可以使用三元组构建堆,方便找到列表中的下一个元素;并且,在某一列表的元素已经被全部添加到堆中后,即可退出循环(之后的区间中都会缺少一个列表的元素,不为合法区间)
代码实现
class Solution {
public:
vector<int> smallestRange(vector<vector<int>>& nums) {
priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<>> pq;
int r = -100001;
for(int i = 0; i < nums.size(); ++i)
{
pq.emplace(nums[i][0], i, 0);
r = max(nums[i][0], r);
}
int ansl = get<0>(pq.top()), ansr = r;
while(true)
{
auto[_, i, j] = pq.top();
if(j == nums[i].size() - 1)
break;
pq.pop();
int t = nums[i][j + 1];
r = max(t, r);
pq.emplace(t, i, j + 1);
int l = get<0>(pq.top());
if(ansr - ansl > r - l)
ansr = r, ansl = l;
}
return {ansl, ansr};
}
};
复杂度分析
- 时间复杂度:$O(n \log k)$,其中 $n$ 为 $nums$ 中所有元素的个数,$k$ 为 $nums$ 数组的长度
- 空间复杂度:$O(k)$,为小根堆使用的空间