LeetCode 632. Smallest Range

题意:给K个list,找出一个最小范围,要求每个list至少包含一个元素。

solution 1. 暴力搜索。

s1 将K个数组进行合并,并排序,记录每一个元素原来属于哪一个数组。也就是说每一个元素都是一个pair<int, int>  value, id
s2 滑动窗口。遍历数组中的每一个元素,以该元素为起点,一步一步寻找窗口的结尾,直到覆盖了所有的数组。
2.1 如何判断是否覆盖了所有的数组?
记录一个count,每有一个新的数组出现count++,count==k时就覆盖了所有的数组
2.2 如何判断这是一个新的数组?
hash表记录pair.second出现的次数
2.3 记录当前最小范围,如果本次搜索已经达到了最小范围-1但是count<k,则跳过进行下一次搜索
2.4 如何计算最小范围?

初始化为pair.back().first - pair.front().first,只要在2.3限制之前count==k,即可更新left,right,min_range = (right.first-left.first)

代码如下:

class Solution {
public:
    vector<int> smallestRange(vector<vector<int>>& nums) {     
        int k = nums.size(); // k lists  
        vector< pair<int, int> > 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<int> 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<int, int> 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<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=INT32_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;  
    }  
};  

该方法可以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<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;
    }
};

这个方法的效率就高了很多,而且行数要明显的少了。

submission:


今天在这道题上花了差不多有一上午的时间,只能自主写出solution 1。深感自己大多时候只能使用暴力算法,对于算法的优化知之甚少,而且对于高级的数据结构可谓知识匮乏,在编程实现算法时,往往会出现思维的混乱导致进度缓慢。

因为目前主要关注的是hash表,因此对于优先队列的知识暂时先放一放。待做到相应练习时再来复习也不晚。

往后遇到复杂的问题,首先要整理出思路,甚至是具体的伪代码。今天就在这里吃了大亏,虽然很快就想出了大致的算法,但是具体的流程和函数的输入输出完全没有思考过,写了很多才发现,原来做了无用功。以后应该要先想好再写,而不是边写边改。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值