详解 LeetCode 242.有效的字母异位词、349. 两个数组的交集、202. 快乐数、1. 两数之和 | 代码随想录 day5 + 哈希表

一.哈希表理论基础

1.哈希表是根据关键码的值直接进行访问的数据结构

2.哈希表通常用来快速判断一个元素是否出现在集合里

3.哈希函数:将其他不同类型的数据格式转化成不同的数值,从而将数据映射到哈希表的索引上。

4.哈希碰撞:两种不同的数据映射到哈希表的同一索引上

解决:拉链法、线性探测法。

5.哈希三种常用数据结构:

(1)数值:适用于不太大的数值

(2)set :适用于较大的数值,不可存放相同的数据,即天然去重。

        multiset :可存放相同的数据。   

                         二者的差别原因:set 的返回值是 pair <iterator, bool>,一个返回的是迭代器,指明插入的位置,另一个返回结果:是否插入成功。所以,当第一个插入成功后,第二个相同数字便不会插入成功。 而 multiset 的返回值只是一个迭代器,故允许相同数据二次插入。

        unordered_set :不可存放相同的数据,即天然去重,容器中元素的排列是无序的。 

(3)map:适用于要存放的数据包括两个部分。

multimap、unordered_map。

  

二.有效的字母异位词

1.题目描述

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

2.思路分析

字母异位词:两个集合中出现的字母相同且出现的字母的次数也完全相同,只是位置不一样。

题目要求:快速判断位于 s 集合中的字母是否出现在 t 集合中,立马想到哈希法。又因为集合中字母的种类最多为26个,所以使用数组会比使用其他两个容器快得多。(使用其他两个容器,还要将每个元素通过相应的哈希函数映射为相应索引。)

既是判断 s 中的字母是否出现在 t 中,那么我们只要统计出 s 集合中,每个字母出现的次数,得到一个存放这些字母出现次数的哈希表,然后再用这个哈希表依次去减 t 集合中 每个字母出现的次数,如果得到最后的哈希表所有元素均为 0,说明两个集合的出现字母的次数相等,即他们是字母异位词,反之,若哈希表中某一单位的值不为 0 ,为正或负,则说明两个集合字母出现次数不一致或字母不一致,那他们就不是字母异味词。

代码:

class Solution {
public:
    bool isAnagram(string s, string t) {
        int hash[26] = {0};  //创建存放26个字母出现次数的哈希表并置空
        for(int i = 0; i < s.size(); i++) {   //统计s集合中出现的字母的次数
            hash[s[i] - 'a']++; 
        }
        for(int i = 0; i < t.size(); i++) {   //利用hash表统计t集合中出现的字母的次数
            hash[t[i] - 'a']--;
        }
        for(int i = 0; i < 26; i++) {   //判断hash表中各元素是否为0
            if(hash[i] != 0)
                return false;
        }
        return true;
    }
};

注意:

需要把字符映射到数组也就是哈希表的索引下标上,我们并不需要知道 26 个字母的ASCII码值,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。

再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。

三.两个数组的交集

1.题目描述

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

2.思路分析

(1)使用 set 容器

数值过大,数组放不下,故使用 set 容器。题目中又要求输出结果中元素唯一,所以我们优先使用unordered_set 。

首先将 nums1 的数据映射到哈希表中,也创建出一个新的哈希表用于存放最后的数据。

将 nums1 的数据映射到哈希表后,我们就开始找 nums2 中的元素是否在 nums1 中出现,是则存放到最后的容器中,不是则继续遍历。

附代码:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //set 方法
        unordered_set<int> result;
        unordered_set<int> nums(nums1.begin(), nums1.end()); //将nums1映射到哈希表中
        for(int i = 0; i < nums2.size(); i++) {
            if(nums.find(nums2[i]) != nums.end()) {  //找到
                result.insert(nums2[i]);
            }
        }
        return vector<int>(result.begin(), result.end());
    }
};

注意:

(1)

成员方法功能
find(key)查找值为key的元素,如果找到,则返回一个指向该元素的正向迭代器;如果没找到,则返回一个与end()方法相同的迭代器
end()返回指向容器中最后一个元素之后位置的迭代器

所以在判断 2 中元素是否在 1 中出现过时,只要判断 nums.find()所返回的迭代器是不是与end()方法相同的迭代器即可,是就是没找到,否就是找到了。

(2)该函数要求返回的值的类型是 vector 型,我们所定义的结果类型是 unordered_set型,所以在最后返回时要做相应的变换。

(2)数组方法

因为力扣题目更新了数据要求,只要不大于1000即可,所以本题也可以使用数组方法进行求解,大致思路同上个题。

唯一不同的是,我们这里只需要让 出现在 1 中的数字在哈希表中相应位置为 1即可,代表这个数字在 1 中存在,并不需要统计他出现的次数。那么在 2 查找时直接查找 2 中元素所对应到哈希表中的位置值是否为1 ,为1则说明这个值在 1 和 2 中都出现过,直接插入到最后的结果容器中即可,(unordered_set 天然去重,所以不必担心元素重复的问题)

这里直接附代码:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
       int hash[1001] = {0};
       unordered_set <int> result;
       for(int i = 0; i < nums1.size(); i++) {
           hash[nums1[i]] = 1;
       }
       for(int i = 0; i < nums2.size(); i++) {
           if(hash[nums2[i]] == 1)
                result.insert(nums2[i]);
       }
       return vector<int>(result.begin(), result.end());
    }
};

四.快乐数

1.题目描述

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false

2.思路分析

从例子中我们可以看到,每做一次求和得到的 sum ,就是下一次求和的数,而在这个过程中,除非得到的sum = 1,否则会一直进行求和下去,有可能就陷入了无限循环。

那么什么情况下,就说明求和sum陷入了无限循环呢?当在做求和运算时,出现了两个相同的sum

那么本题的思路非常清晰了:将所有求出来的 sum 列到哈希表中,每次算出一个新的sum时,都要去表中查询是否有相同的值,如果有,则证明陷入循环中,立即退出;如果没有,更新要求和的值,即上一轮求出来的新sum,进行下一轮的求和。

附代码:

class Solution {
public:

    int getSum(int n) {  //求和函数
        int sum = 0;
        while(n) {
            sum += (n % 10) * (n % 10);
            n = n / 10;
        }
        return sum;
    }

    bool isHappy(int n) {
        unordered_set<int> result;
        while(1) {
            int sum = getSum(n);
            if(sum == 1){   //sum = 1,是快乐数,退出循环
                return true;
            } 
            else if(result.find(sum) != result.end()) {   //sum!=1,判断他是否和之前的sum重复
                return false;
            }
            else result.insert(sum);  //不重复,则将新sum插入到容器中
            n = sum;   //更新n,进行下一轮循环
        }

    }
};

五.两数之和

1.题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

2.思路分析

此时我们要返回的是两个数的下标,但又要保存遍历过的数字,所以只能使用map容器。

利用 map 容器存放我们已经遍历过的数值及其下标,再进行下一次的遍历时,我们先从 map 容器中找,看是否有能与他相加等于 target 的值,若有,直接返回两数的下标;若没有,则将当前值和下标存放到 map 容器中,再进行下一个数值的遍历,直到找到第一对满足要求的数值或者找不到为止。

附代码:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> result;
        for(int i = 0; i < nums.size(); i++) {
            int s = target - nums[i];   //与目标值相加为target的值,也是我们要在map容器中寻找的值
            ///auto it = result.find(s);
            if(result.find(s) != result.end()) {   //找到了    也可以这样写 if(it != result.end())
                return {result.find(s)->second, i};   也可写成: return {it->second, i};
            }
            else {   //没找到,将当前值插入到map中
                result.insert(pair<int, int>(nums[i], i));
            }
        }
        return {};
        
    }
};

注意:

(1)return 的值,要返回两个变量下标:在map中找到的符合条件的数值下标和当前数值的下标,在返回map容器中符合条件的数值下标时要注意,我们不是任意返回的,而是要返回 找到的数值的下标,找到的数值即 result.find(s),所以在返回时 是它的第二个值。也可以直接将找到的函数值定义为auto类型的it,更方便书写。

(2)在没找到时,要将当前值插入到map中,再插入时我们要注意,一次要同时插入两个值:第一个值是当前元素值 key,第二个值是当前元素的下标 value。因为要同时插入,所以要使用pair容器进行插入。


day5练习完成!

最近事真的好多!除了周六日,都没有连续完整的时间来练习coding了,不过碎片化的时间也有在好好学习。加油吧!

哈希表真的很不熟悉,所以要学习的和已经学习到的东西都非常多!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值