题目
632. 最小区间
我的思路
这道题思路其实很简单,但要学会合理选择数据结构加快降低时间复杂度。起初我的的解决方案在最后几个比较庞大的测试用例下发生了超时的问题。
两种思路(官方题解):
思路一:
给定 k 个列表,需要找到最小区间,使得每个列表都至少有一个数在该区间中。该问题可以转化为,从 k个列表中各取一个数,使得这 k 个数中的最大值与最小值的差最小。
由于 k 个列表都是升序排列的,因此对每个列表维护一个指针。每次选择这些指针中对应元素val最小的向右滑动,更新该指针,并重新计算当前的区间大小。如果区间大小比记录的最小的情况还小,那么记录这个最新的。直到某个指针滑出所在列表(表示该列表最大元素作为区间下界的情况已经考虑完毕,不会有下界更大且满足条件的区间)。
考虑复杂度,最坏情况下会有k(列表数)*n(列表平均长度)次滑动,也就是会有k*n次删除,插入,排序这样的操作。也就是那么如何维护这k个指针呢会有较大的影响。因为涉及频繁的删除,插入,并且经常操作最大最小的元素,那么显然堆(注意堆和平衡二叉树,二叉排序树的区别优劣)最合适。而堆在C++中可以用优先队列实现优先级队列priority_queue(重载运算符,优先级比较)。
那么时间复杂度就是k*n*logk,空间复杂度是k
(顺带提一下C++中容器的低层数据结构2)
class Solution { public: vector<int> smallestRange(vectorint>>& nums) { int rangeLeft = 0, rangeRight = INT_MAX; int size = nums.size(); vector<int> next(size); auto cmp = [&](const int& u, const int& v) { return nums[u][next[u]] > nums[v][next[v]]; }; priority_queue<int, vector<int>, decltype(cmp)> pq(cmp); int minValue = 0, maxValue = INT_MIN; for (int i = 0; i < size; ++i) { pq.emplace(i); maxValue = max(maxValue, nums[i][0]); } while (true) { int row = pq.top(); pq.pop(); minValue = nums[row][next[row]]; if (maxValue - minValue < rangeRight - rangeLeft) { rangeLeft = minValue; rangeRight = maxValue; } if (next[row] == nums[row].size() - 1) { break; } ++next[row]; maxValue = max(maxValue, nums[row][next[row]]); pq.emplace(row); } return {rangeLeft, rangeRight}; } }; 作者:LeetCode-Solution 链接:https://leetcode-cn.com/problems/smallest-range-covering-elements-from-k-lists/solution/zui-xiao-qu-jian-by-leetcode-solution/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
思路二:滑动窗口+哈希表
和我的思路有点类似不过数据结构不太一样。
总体思路都是:把k个列表中出现过的元素从小到大全部排列,同时要记住各个元素在原来的k个列表中哪几个列表中出现过。然后滑动窗口,保证窗口中的元素在所有k个列表中都存在。方法是:维护各个列表中在当前窗口中元素的个数。
那么在我最初的实现中:时间复杂度是k*n*k+2*k*n也就是k^2*n,并不理想。我当时排列所有元素时,每一轮都是是遍历k个列表中的最小元素,得最小的插入一个队列vector。我的改进是用set(也就是二叉平衡树、红黑树)来存这个包含所有元素的队列,那么每次插入的复杂度就是logk。同时set(二叉平衡树实现)容器可以使用迭代器(priority_queue不行,我猜因为底层结构是堆,不方便排序,只是方便最大最小元素的增删查)。
所以改进后时间复杂度是k*n*logk,空间复杂度是k*n。代码见(我的实现)
我觉得官方的思路更好一点,使用哈希表。
把k个列表中所有出现过的元素作为哈希表的键,映射到一个列表,存储该元素所在的列表号。这样滑动窗口的指针是指向哈希表的键,(可以通过count()判断是否存在该键)。同样在滑动的同时,维护各个列表在当前窗口中元素的个数。
这样一来复杂度:
class Solution { public: vector<int> smallestRange(vectorint>>& nums) { int n = nums.size(); unordered_map<int, vector<int>> indices; int xMin = INT_MAX, xMax = INT_MIN; for (int i = 0; i < n; ++i) { for (const int& x: nums[i]) { indices[x].push_back(i); xMin = min(xMin, x); xMax = max(xMax, x); } } vector<int> freq(n); int inside = 0; int left = xMin, right = xMin - 1; int bestLeft = xMin, bestRight = xMax; while (right < xMax) { ++right; if (indices.count(right)) { for (const int& x: indices[right]) { ++freq[x]; if (freq[x] == 1) { ++inside; } } while (inside == n) { if (right - left < bestRight - bestLeft) { bestLeft = left; bestRight = right; } if (indices.count(left)) { for (const int& x: indices[left]) { --freq[x]; if (freq[x] == 0) { --inside; } } } ++left; } } } return {bestLeft, bestRight}; } }; 作者:LeetCode-Solution 链接:https://leetcode-cn.com/problems/smallest-range-covering-elements-from-k-lists/solution/zui-xiao-qu-jian-by-leetcode-solution/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
我的实现
class Solution { public: vector<int> smallestRange(vectorint>>& nums) { struct pairComp { bool operator() (const pair<int,int>& lhs, const pair<int,int>& rhs) const{ if( lhs.first < rhs.first)return true; else if(lhs.first==rhs.first &&lhs.second return true; else return false; //return lhs.first <= rhs.first && lhs.second } }; //从小到大遍历一遍这个k个升序的整数数组,把它们的元素按大小排在一个向量vector ,int>,pairComp> allelements; bool empty_check; int min_val; int no_nums; //cout<<"check!"<> allelements中。 vector<int> result(2); setint empty_check = true; min_val = INT_MAX; for(auto it_nums = nums.begin(); it_nums!=nums.end();it_nums++){ int dis = distance(nums.begin(),it_nums); for(auto it_val:*it_nums){ allelements.insert(make_pair(it_val,dis)); } } /*while(1){ for(auto it_nums = nums.begin(); it_nums!=nums.end();it_nums++){ if(!(*it_nums).empty()){//该数组还剩余有元素 if(*(*it_nums).begin()<=min_val){ min_val = *(*it_nums).begin(); no_nums = distance(nums.begin(),it_nums); empty_check = false; } } } if(empty_check==false){ allelements.insert(make_pair(min_val,no_nums)); nums[no_nums].erase(nums[no_nums].begin()); //cout< */ //for(auto it:allelements) //cout << it.first<<"\t"< //用两个指针指向allelements的元素,并让这两个指针指向区间的边界: auto it_lower = allelements.begin(); auto it_upper = allelements.begin(); int nums_size = nums.size(); int check_allin = 0; int min_gap = -1; vector<int> check_eachin(nums_size,0); //把后边界指针向后滑动,滑动的过程中,用k个变量来标记当前区间各个数组的元素在区间中出线的次数 while(check_allin<nums_size){ //判断是否越界,其实不必要??? //cout<<(*it_upper).second< if(check_eachin[(*it_upper).second]==0){ ++check_allin; } check_eachin[(*it_upper).second]++; it_upper++; } it_upper--; //把首址针向后滑动,直到某个标记等于0之前 while(1){ while(check_eachin[(*it_lower).second]>1){ check_eachin[(*it_lower).second]--; it_lower++; } if(min_gap==-1||min_gap>(*it_upper).first-(*it_lower).first){ min_gap=(*it_upper).first-(*it_lower).first; result[0]=(*it_lower).first; result[1]=(*it_upper).first; } if((++it_upper)!=allelements.end()){ //it_upper++; check_eachin[(*it_upper).second]++; }else{ break; } } return result; } }; /* 一个比较朴素的思路: 从小到大遍历一遍这个k个升序的整数数组,把它们的元素按大小排在一个向量vector > allelements中。 用两个指针指向allelements的元素,并让这两个指针指向区间的边界: 首先都指向首元素跳过 把后边界指针向后滑动,滑动的过程中,用k个变量来标记当前区间各个数组的元素在区间中出线的次数 直到滑动到所有k个标记都大于0 把首址针向后滑动,直到某个标记等于0之前 (注意滑动时若遇到连续多个想同val的元素,那么直接滑到该连续序列的最后一个) 提审错了,需要找到最短的,所以方法是交替滑动上下边界的指针,保留距离最短的一组 */
拓展学习
在初始化set等容器时,重载小于运算符,实现更复杂对象的排序。
https://www.cnblogs.com/litaozijin/p/6665595.html