为了账号安全,请及时绑定邮箱和手机立即绑定

leetcode题解(查找表问题)

标签:
C++ 算法

查找,是使用计算机处理问题时的一个最基本的任务,因此也是面试中非常常见的一类问题。很多算法问题的本质,就是要能够高效查找。学会使用系统库中的map和set,就已经成功了一半。

<!--more-->

set的使用

两类查找问题

  • 查找有无:元素’a’是否存在?set;集合
  • 查找对应关系(键值对应):元素’a’出现了几次?map;字典
  • 通常语言的标准库中都内置set和map
  • 容器类
    • 屏蔽实现细节
    • 了解语言中标准库里常见容器类的使用

常见操作:

  • insert
  • find
  • erase:删除
  • change (map)

leetcode349. 两个数组的交集

paste

  • 结果中每个元素只能出现一次
  • 出现的顺序可以是任意的

代码实现


    // 349. Intersection of Two Arrays
    // 时间复杂度:O(nlogn)
    // 空间复杂度:O(n)
    class Solution {
    public:
        vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
            //O(nlogn)

            set<int> record;
            for( int i = 0 ; i < nums1.size() ; i ++ )
                record.insert(nums1[i]);
            //O(nlogn)
            set<int> resultSet;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record.find( nums2[i] ) != record.end() )
                    resultSet.insert( nums2[i] );
            //o(n)
            vector<int> resultVector;
            for(set<int>::iterator iter = resultSet.begin() ; iter != resultSet.end() ; iter ++ )
                resultVector.push_back( *iter );

            return resultVector;
        }
    };

改写程序:

    // 349. Intersection of Two Arrays
    class Solution {
    public:
        vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {

            set<int> record(nums1.begin(), nums1.end());

            set<int> resultSet;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record.find( nums2[i] ) != record.end() )
                    resultSet.insert( nums2[i] );

            return vector<int>(resultSet.begin(), resultSet.end());
        }
    };
map的使用

leetcode350. 两个数组的交集 II

paste

代码实现


    // 350. Intersection of Two Arrays II
    // 时间复杂度:O(nlogn)
    // 空间复杂度:O(n)
    class Solution {
    public:
        vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {

            map<int, int> record;
            //O(nlogn)
            for( int i = 0 ; i < nums1.size() ; i ++ )
                record[nums1[i]] += 1;

            //o(nlogn)
            vector<int> resultVector;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record[ nums2[i] ] > 0 ){
                    resultVector.push_back( nums2[i] );
                    record[nums2[i]] --;
                }

            return resultVector;
        }
    };

思考

  • 数组有序如何改写?
set和map不同底层实现的区别

常见操作:

  • insert
  • find
  • erase
  • change (map)

  • set和map可以有不同的底层实现

底层实现

哈希表

哈希表不管查找、插入、删除都是O(1)的时间复杂度。

  • 哈希表的缺点是失去了数据的顺序性

数据的顺序性

  • 数据集中的最大值和最小值
  • 某个元素的前驱和后继
  • 某个元素的floor和ceil
  • 某个元素的排位rank
  • 选择某个排位的元素select

  • map和set的底层实现为平衡二叉树
  • unordered_map和unordered_set的底层实现为哈希表

改用哈希表实现的代码


    #include <unordered_set>
    using namespace std;

    // 349. Intersection of Two Arrays
    // 时间复杂度:O(n)
    // 空间复杂度:O(n)
    class Solution {
    public:
        vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
            // O(n)
            unordered_set<int> record(nums1.begin(), nums1.end());
            // O(n)
            unordered_set<int> resultSet;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record.find( nums2[i] ) != record.end() )
                    resultSet.insert( nums2[i] );
            // O(n)
            return vector<int>(resultSet.begin(), resultSet.end());
        }
    };

    //改写过使用hash表实现底层的unorder_map

    #include <unordered_map>
    using namespace std;

    /// 350. Intersection of Two Arrays II
    // 时间复杂度:O(n)
    // 空间复杂度:O(n)
    class Solution {
    public:
        vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
            // O(n)
            unordered_map<int, int> record;
            for( int i = 0 ; i < nums1.size() ; i ++ )
                record[nums1[i]] += 1;
            // O(n)
            vector<int> resultVector;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record[ nums2[i] ] > 0 ){
                    resultVector.push_back( nums2[i] );
                    record[nums2[i]] --;
                }
            // O(n)
            return resultVector;
        }
    };

相似题目

  • leetcode242 Valid Anagram

Anagram:一个字符串中的字母调整顺序后与原字符串一致。

  • 空串
  • 字符集
  • leetcode202 Happy Number

判断一个数是否为happy number。happy number是指,一个数,将其替换为其各位数字的平方和,重复这个过程,如果最终能得到1,这是happy number,如果这个过程陷入了一个不包含1的循环,则不是happy number

判断一个数是否为happy number。以19为例:

1^2 + 9^2 = 82

8^2 + 2^2 = 68

6^2 + 8^2 = 100

1^2 + 0^2 + 0^2 = 1 Happy Number!
  • leetcode290 Word Pattern

给出一个模式(pattern)以及一个字符串,判断这个字符串是否符合模式?


如pattern=“abba”,str=“dog cat cat dog”,返回true

如pattern=“abba”,str=“dog cat cat fish”,返回false

字符集?

空串符合任意模式?还是不符合任意模式?
  • leetcode205 Isomorphic Strings

判断两个字符串是否同构?
如果我们能够寻找到一个字符集到字符集的映射,使得通过这个字符集的映射,s可以转变为t,则称为s和t同构。


如 egg 和 add,返回true
如 foo 和 bar,返回false
如 paper 和 title,返回true

注意

  • 字符集?
  • 空串
  • 是否可以一个字母映射到自己?

  • 451 Sort Characters By Frequency

给定一个字符串,按照字母出现频率的倒序重组整个字符串

如“tree”,返回“eert”
如“cccaaa”,返回“cccaaa”
如“Aabb”,返回“bbAa”
对于相同频次的字母,顺序任意。大小写敏感。
一个使用查找表的经典问题

leetcode1 两数之和

paste

注意

  • 索引从0开始计算还是从1开始计算?
  • 没有解怎么办?
  • 有多个解怎么办? 保证有唯一解

暴力解法:O(n^2)

  • 遍历所有数据对。判断是否等于target。

双索引对撞

  • 排序后,使用双索引对撞:O(nlogn) + O(n) = O(nlogn)

使用查找表

  • 查找表。将所有元素放入查找表,之后对于每一个元素a,查找 target - a 是否存在。存在的话索引是谁。
  • 将所有元素放入查找表 - 不行
  • 只把v前面的放入查找表

    //时间复杂度:O(n)
    //空间复杂度:O(n)
    class Solution {
    public:
        vector<int> twoSum(vector<int>& nums, int target) {

            unordered_map<int,int> record;
            for( int i = 0 ; i < nums.size() ; i ++ ){

                int complement = target - nums[i];
                if( record.find(complement) != record.end() ){
                    int res[] = {i, record[complement]};
                    return vector<int>(res, res + 2);
                }

                record[nums[i]] = i;
            }

            throw invalid_argument("the input has no solution");
        }
    };

相似问题

leetcode15 3Sum

给出一个整形数组,寻找其中的所有不同的三元组(a,b,c),使得a+b+c=0
如 nums = [-1, 0, 1, 2, -1, -4]
结果为[ [-1, 0, 1], [-1,-1,2] ]

  • 不同的三元组?
  • 如果有多个解,解的顺序?
  • 如果没有解?

leetcode18 4Sum

给出一个整形数组,寻找其中的所有不同的四元组(a,b,c,d),使得a+b+c+d 等于一个给定的数字target。
如 nums = [1, 0, -1, 0, -2, 2],target = 0
结果为[ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]

leetcode16 3Sum Closest

给出一个整形数组,寻找其中的三个元素a,b,c,使得a+b+c的值最接近另外一个给定的数字target

如 nums = [-1, 2, 1, -4],target = 1
结果为2 ( -1 + 2 + 1 = 2 )

注意

  • 如果有多个解,其和target值的接近程度一样怎么办?

  • 如果没解?(可不可能没解?)
灵活选择键值

leetcode454 4Sum II

paste

暴力解法:O(n^4)

500^4 = 625,0000,0000

将D中的元素放入查找表:O(n^3)

500^3 = 1,2500,0000

将C+D的每一种可能放入查找表:O(n^2)

  • 500^2 = 25,0000
  • 要记录每一种和出现了多少次,所以使用map
代码实现:
    //时间复杂度O(n^2)
    //空间复杂度O(n^2)
    class Solution {
    public:
        int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {

            assert( A.size() == B.size() && B.size() == C.size() && C.size() == D.size() );
            unordered_map<int,int> hashtable;
            for( int i = 0 ; i < C.size() ; i ++ )
                for( int j = 0 ; j < D.size() ; j ++ )
                    hashtable[C[i]+D[j]] += 1;

            int res = 0;
            for( int i = 0 ; i < A.size() ; i ++ )
                for( int j = 0 ; j < B.size() ; j ++ )
                    if( hashtable.find(-A[i]-B[j]) != hashtable.end() )
                        res += hashtable[-A[i]-B[j]];

            return res;
        }
    };

思考

  • 将A+B和C+D的每一种可能放入两个查找表:O(n^2)

相似问题

  • leetcode49 Group Anagrams

给出一个字符串数组,将其中所有可以通过颠倒字符顺序产生相同结果的单词进行分组。

如 [ “eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
返回[ [“ate”, “eat”, “tea”], [“nat”, “tan”], [“bat”] ]

注意

  • 字符集
  • 大小写敏感
灵活选择键值2

leetcode447 回旋镖的数量(Number of Boomerangs)

paste

暴力解法:O(n^3)

  • 三重循环枚举所有的可能性。

使用查找表

  • 观察到 i 是一个“枢纽”,对于每个点i,遍历其余点到i的距离
  • 对于每个枢纽i,计算它到其它点j的距离,并将距离作为键存入map中,value为距离的个数。
  • O(n^2)

    //时间复杂度:O(n^2)
    //空间复杂度:O(n)
    class Solution {
    public:
        int numberOfBoomerangs(vector<pair<int, int>>& points) {

            int res = 0;
            for( int i = 0 ; i < points.size() ; i ++ ){

                // record中存储 点i 到所有其他点的距离出现的频次
                unordered_map<int, int> record;
                for( int j = 0 ; j < points.size() ; j ++ )
                    if( j != i )
                        record[dis(points[i], points[j])] += 1;

                for( unordered_map<int, int>::iterator iter = record.begin() ; iter != record.end() ; iter ++ )
                    res += (iter->second)*(iter->second-1);
            }
            return res;
        }

    private:
        int dis( const pair<int,int> &pa, const pair<int,int> &pb){
            return (pa.first - pb.first) * (pa.first - pb.first) +
                   (pa.second - pb.second) * (pa.second - pb.second);
        }
    };

相似问题

leetcode149 Max Points on a Line

给出2D平面上的n个点,求出最多有多少个点在一条直线上?

  • 点坐标的范围
  • 点坐标的表示(整数?浮点数?浮点误差?)
查找表和滑动窗口

paste

暴力解法:O(n^2)

  • 时间性能不满足

滑动窗口

  • 结合使用滑动窗口和查找表,不断查找当前滑动窗口内有没有重复值。

    // 时间复杂度: O(n)
    // 空间复杂度: O(k)
    class Solution {
    public:
        bool containsNearbyDuplicate(vector<int>& nums, int k) {

            if( nums.size() <= 1 )
                return false;

            if( k <= 0 )
                return false;

            unordered_set<int> record;
            for( int i = 0 ; i < nums.size() ; i ++ ){

                if( record.find( nums[i] ) != record.end() )
                    return true;

                record.insert( nums[i] );

                // 保持record中最多有k个元素
                // 因为在下一次循环中会添加一个新元素,使得总共考虑k+1个元素
                if( record.size() == k + 1 )
                    //删除掉最左侧元素
                    record.erase( nums[i-k] );
            }

            return false;
        }
    };

相似问题

  • leetcode217 Contains Duplicate

给出一个整形数组,若数组中存在相同的元素,则返回true,否则返回false.

二分搜索树底层实现的顺序性

leetcode220. 存在重复元素 III

paste

思想

维持滑动窗的大小为k
遍历每一个元素,在活动窗口中寻找|v-nums[i]| < t, 即窗口中的元素范围为:[v-t...v+t]之间。采用ceil和floor可以实现

代码实现


    // 时间复杂度: O(nlogn)
    // 空间复杂度: O(k)
    class Solution {
    public:
        bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {

            set<long long> record;
            for( int i = 0 ; i < nums.size() ; i ++ ){

                if( record.lower_bound( (long long)nums[i] - (long long)t ) != record.end() &&
                    *record.lower_bound( (long long)nums[i] - (long long)t ) <= (long long)nums[i] + (long long)t )
                    return true;

                record.insert( nums[i] );

                if( record.size() == k + 1 )
                    record.erase( nums[i-k] );
            }

            return false;
        }
    };

原创始发于慕课网

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
PHP开发工程师
手记
粉丝
8991
获赞与收藏
336

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消