第三章 哈希表

哈希解决问题一般有三种数据结构供选择:

  • 数组
  • map(映射)
  • set(集合)
    在这里插入图片描述在这里插入图片描述

一、有效的字母异位词

Leetcode 242

数组就是一个简单的哈希表,使用数组比使用 map 或者 unordered_map 更省时间和空间,因为后者需要创建哈希函数以及维护哈希表 or 红黑树。

class Solution {
public:
    bool isAnagram(string s, string t) {
        int record[30] = {0};
        for (char c: s) 
            record[c - 'a'] ++ ;
        for (char c: t)
            record[c - 'a'] -- ;
        for (int x: record)
            if (x) return false;
        return true;
    }
};

1.1 赎金信

Leetcode 383

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[30] = {0};
        for (char c: magazine) 
            record[c - 'a'] ++ ;
        for (char c: ransomNote) {
            if (!record[c - 'a'])
                return false;
            record[c - 'a'] -- ;
        }     
        return true;
    }
};

1.2 字符异位词分组

Leetcode 49

核心思路:找一个中间状态,让相同字符异位词均能映射到上面。这里将每个字符按照其 ASCII 码的大小排序后,所有相同的字符处在一个组内部即可。

move() 的作用:本来需要将整个字符串 copy 过去,使用move之后只需要把字符串的首地址赋值过去,可以减少一次拷贝操作,提高效率。

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> dict;
        for (auto &str: strs) {
            string key = str;
            sort(key.begin(), key.end());
            dict[key].push_back(move(str));
        }
        vector<vector<string>> res;
        for (auto it = dict.begin(); it != dict.end(); it ++ )
            res.push_back(move(it->second));
        return res;
    }
};

时间复杂度: N N N 表示字符串个数, L L L 表示字符串平均长度。对于每个字符串,哈希表和 v e c t o r vector vector 的插入操作时间复杂度为 O ( 1 ) O(1) O(1),排序时间时间复杂度为 L l o g ( L ) Llog(L) Llog(L),所以总共时间复杂度为 O ( N L l o g L ) O(NLlogL) O(NLlogL)

1.3 找到字符串中所有字母异位词

Leetcode 438

使用一个哈希表来维护字符,先将字符串 p 中所有字符添加进哈希表中,然后使用一个滑动窗口来遍历字符串 s,字符进入窗口相应元素数目减一,出窗口加一。当元素数目为零的时候,表示该元素匹配。

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        unordered_map<char, int> cnt;
        for (auto c: p) cnt[c] ++ ;
        int tot = cnt.size(); // 记录种类数目
        vector<int> res;
        for (int l = 0, r = 0, satisfy = 0; r < s.size(); r ++ ) { // satisfy指当前窗口内部满足要求的种类数
            if ( -- cnt[s[r]] == 0) satisfy ++ ;
            if (r - l + 1 > p.size()) {
                if (cnt[s[l]] == 0) satisfy -- ;
                cnt[s[l ++ ]] ++ ;
            }
            if (satisfy == tot) res.push_back(l);
        }
        return res;
    }
};

二、两个数组的交集

Leetcode 349

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set;
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        for (int x: nums2) 
            if (nums_set.find(x) != nums_set.end())
                result_set.insert(x);
        return vector<int>(result_set.begin(), result_set.end());
    }
};

2.1 两个数组的交集 II

Leetcode 350

方法一:哈希表(适用于进阶问题二)

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        if (nums1.size() > nums2.size()) return intersect(nums2, nums1);
        unordered_map<int, int> cnt; // 节省空间,只保存较小长度的数组
        for (int x: nums1) cnt[x] ++ ;
        vector<int> res;
        for (int x: nums2) 
            if (cnt.count(x)) {
                res.push_back(x);
                cnt[x] -- ;
                if (!cnt[x]) cnt.erase(x);
            }
        return res;
    }
};
  • 时间复杂度: O ( m + n ) O(m + n) O(m+n)
  • 空间复杂度: O ( m i n ( m + n ) ) O(min(m +n)) O(min(m+n))

方法二:双指针(适用于进阶问题一)

先对数组排序,然后使用两个指针分别指向两个数组开始,如果两个指针指向元素不相等,将数值较小的指针右移。如果两个指针指向元素相同,则将该元素加入结果中,并同时右移两个指针。当至少有一个指针超出数组范围结束。

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        sort(nums1.begin(), nums1.end());
        sort(nums2.begin(), nums2.end());
        vector<int> res;
        for (int i = 0, j = 0; i < nums1.size() && j < nums2.size();) {
            if (nums1[i] == nums2[j]) {
                res.push_back(nums1[i]);
                i ++ , j ++ ;
            } else if (nums1[i] < nums2[j]) i ++ ;
            else j ++ ;
        }
        return res;
    }
};
  • 时间复杂度: O ( m   l o g   m + n   l o g   n ) O(m\ log\ m + n\ log\ n) O(m log m+n log n)
  • 空间复杂度: O ( m i n ( m , n ) ) O(min(m,n)) O(min(m,n))

三、快乐数

Leetcode 202

提示:如果出现过的数字再重复出现就进入循环了

注意,最终结果会存在三种可能:

  • 最终得到 1 1 1
  • 最终进入循环
  • 值会越来越大,接近无穷大

对于第三种情况,考虑每一位数的最大数字的下一个数(即每一位数平方之和)是多少。

几位数最大数字下一个数
1981
299162
3999243
49999324
1399999999999991053

在三位数的情况下,其各位数的平方和不可能大于 243 243 243,要么被困在 243 243 243 以下的循环内,要么跌到 1 1 1。所以对于四位数或者四位以上的数字,其在每一步(各位数平方和)之后都会丢失一位,直到降到 3 3 3 位为止。所以可以发现第三种出现无穷大的情况使不可能的。

class Solution {
public:
    int getSum(int n) {
        int sum = 0;
        while (n) {
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n) {
        unordered_set<int> st;
        while (true) {
            int sum = getSum(n);
            if (sum == 1) return true;
            if (st.find(sum) != st.end()) return false;
            else st.insert(sum);
            n = sum;
        }
    }
};

四、两数之和

Leetcode 1

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> mp;
        for (int i = 0; i < nums.size(); i ++ ) {
            auto it = mp.find(target - nums[i]);
            if (it != mp.end()) return {it->second, i};
            mp[nums[i]] = i;
        }
        return {};
    }
};

五、四数相加 II

Leetcode 454

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> mp; // 记录数组1与数组2元素 (和,出现次数)
        for (int a: nums1) 
            for (int b: nums2)
                mp[a + b] ++ ;
        int count = 0;
        for (int c: nums3)
            for (int d: nums4)
                if (mp.find(0 - (c + d)) != mp.end())
                    count += mp[0 - (c + d)];
        return count;
    }
};

六、三数之和

Leetcode 15

这个题与上一题的区别在于,这个的结果不能重复。

如果使用哈希表,这个题目不好判重,会非常麻烦,细节很多。所以这里使用双指针的思路。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i ++ ) {
            if (i && nums[i] == nums[i - 1]) continue;
            if (nums[i] > 0) break;
            for (int l = i + 1, r = nums.size() - 1; l < r; ) // 双指针
                if (l < r && nums[l] + nums[r] + nums[i] > 0) r -- ;
                else if (l < r && nums[l] + nums[r] + nums[i] < 0) l ++ ;
                else {
                    res.push_back({nums[i], nums[l], nums[r]});
                    while (l < r && nums[l] == nums[l + 1]) l ++ ;
                    while (l < r && nums[r] == nums[r - 1]) r -- ;
                    l ++ , r -- ;
                }
        }
        return res;
    }
};

七、四数之和

Leetcode 18

这个题目和上一个题目类似,结果依然使不能重复的,唯一区别在于多了一层 for 循环。

注意四个数相加可能会溢出

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        for (int i = 0; i < nums.size(); i ++ ) {
            if (i && nums[i - 1] == nums[i]) continue;
            if (nums[i] >= 0 && nums[i] > target) break;
            for (int j = i + 1; j < nums.size(); j ++ ) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                if (nums[i] + nums[j] >= 0 && nums[i] + nums[j] > target) break;
                for (int l = j + 1, r = nums.size() - 1; l < r; ) // 双指针
                    if ((long long) nums[l] + nums[r] + nums[i] + nums[j] > target) r -- ;
                    else if ((long long) nums[l] + nums[r] + nums[i] + nums[j] < target) l ++ ;
                    else {
                        res.push_back({nums[i], nums[j], nums[l], nums[r]});
                        
                        // 去重
                        while (l < r && nums[l] == nums[l + 1]) l ++ ;
                        while (l < r && nums[r] == nums[r - 1]) r -- ;

                        // 找到答案,同时更新
                        l ++ , r -- ;
                    }
            }
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值