题意:给K个list,找出一个最小范围,要求每个list至少包含一个元素。
solution 1. 暴力搜索。
s1 将K个数组进行合并,并排序,记录每一个元素原来属于哪一个数组。也就是说每一个元素都是一个pair
s2 滑动窗口。遍历数组中的每一个元素,以该元素为起点,一步一步寻找窗口的结尾,直到覆盖了所有的数组。
2.1 如何判断是否覆盖了所有的数组?
记录一个count,每有一个新的数组出现count++,count==k时就覆盖了所有的数组
2.2 如何判断这是一个新的数组?
hash表记录pair.second出现的次数
2.3 记录当前最小范围,如果本次搜索已经达到了最小范围-1但是count
初始化为pair.back().first - pair.front().first,只要在2.3限制之前count==k,即可更新left,right,min_range = (right.first-left.first)
代码如下:
class Solution {
public:
vector smallestRange(vector>& nums) {
int k = nums.size(); // k lists
vector< pair > merge_list; // value-array id
for ( int i = 0; i < k; i++ ) {
for ( auto v : nums[i] ) {
merge_list.push_back( make_pair( v, i ) );
}
}
sort(merge_list.begin(), merge_list.end());
int n = merge_list.size(); // n elements
// value
vector res;
int left = merge_list.front().first;
int right = merge_list.back().first;
int min_range = right - left;
// search min_range for every lemment
for ( int i = 0; i < n - 1; i++ ) {
unordered_map hash;
hash[merge_list[i].second] = 1;
int count = k - 1;
// 处理i后的元素j
for ( int j = i + 1; j < n && merge_list[j].first-merge_list[i].first < min_range; j++ ) {
// 若该元素属于未出现的数组,那么count--
if ( hash.find(merge_list[j].second) == hash.end() ) {
hash[ merge_list[j].second ] = 1;
count--;
if ( count == 0 ) {
left = merge_list[i].first;
right = merge_list[j].first;
min_range = right - left;
break;
}
}
}
}
res.push_back(left);
res.push_back(right);
return res;
}
};
算法是正确的,然而太过复杂,TLE了(84/86)。
solution 2. 更巧妙的暴力搜索。这个方法是网上找到的:
用两个指针left和right来确定滑动窗口的范围,我们还要用一个哈希表来建立每个数组与其数组中数字出现的个数之间的映射,变量cnt表示当前窗口中的数字覆盖了几个数组,diff为窗口的大小,我们让right向右滑动,然后判断如果right指向的数字所在数组没有被覆盖到,cnt自增1,然后哈希表中对应的数组出现次数自增1,然后我们循环判断如果cnt此时为k(数组的个数)且left不大于right,那么我们用当前窗口的范围来更新结果,然后此时我们想缩小窗口,通过将left向右移,移动之前需要减小哈希表中的映射值,因为我们去除了数字,如果此时映射值为0了,说明我们有个数组无法覆盖到了,cnt就要自减1。这样遍历后我们就能得到最小的范围了,参见代码如下:
class Solution {
public:
vector smallestRange(vector>& nums) {
vector res;
vector> v;
unordered_map m;
for(int i=0;iv[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;
}
};
该方法可以AC,但是效率一般:
submission:
solution 3. 优先队列。这个方法也是网上找到的:
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 smallestRange(vector>& nums) {
int curMax = INT_MIN, n = nums.size();
vector idx(n, 0);
auto cmp = [](pair& a, pair& b) {return a.first > b.first;};
priority_queue, vector>, 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 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;
}
};
这个方法的效率就高了很多,而且行数要明显的少了。
submission:
今天在这道题上花了差不多有一上午的时间,只能自主写出solution 1。深感自己大多时候只能使用暴力算法,对于算法的优化知之甚少,而且对于高级的数据结构可谓知识匮乏,在编程实现算法时,往往会出现思维的混乱导致进度缓慢。
因为目前主要关注的是hash表,因此对于优先队列的知识暂时先放一放。待做到相应练习时再来复习也不晚。
往后遇到复杂的问题,首先要整理出思路,甚至是具体的伪代码。今天就在这里吃了大亏,虽然很快就想出了大致的算法,但是具体的流程和函数的输入输出完全没有思考过,写了很多才发现,原来做了无用功。以后应该要先想好再写,而不是边写边改。