力扣哈希表题目

哈希表 类型题

一、用数组来当作哈希表

二、用set来当作哈希表(unordered_set)

三、用map来当作哈希表(unordered_map)

四、三数之和、四数之和问题(找到不同的元组)(双指针法)

哈希表,是一种逻辑上的定义。通过哈希值可以找到对应需要的内容。例如:26个英文字母,每个英文字母作为一个关键字,可以分别对应一个数组的下标,数组中可以存放英文字母对应的东西,从而构造出哈希表,通过下标既可以找到对应的内容。

哈希表的实现方式:可以使用数组array实现,可以set或者map来实现。数组、集合、映射。set是集合(非重复),可以通过find()方法找到key是否在集合中,适合哈希表。map本身就是key - value的结构,也可以通过map容器的find()方法找到对应key的value,也很适合哈希表。

当哈希值有限时,例如26个英文字母,就可以直接使用数组来构建哈希表

当哈希值较大时,就使用set或者map,一般当只需要一个key时就用set,当需要两个值对应时就需要用到map。

一、用数组来做哈希表

  1. No242.有效的字母异位词

力扣题目链接

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

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

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

说明:你可以假设字符串只包含小写字母。

思路:

字母异位词的意义是,两个字符串所包含的字母应该完全一致。也就是说通过记录两个字符串中所含字母的数量是否一致,可以得出结论。

使用一个数组record[26] 作为哈希表,哈希表的索引就是数组的下标,通过关键字为a-z26个英文字母,分别对应0-25 这 26个哈希值。哈希表的内容就是数组的内容,也就是存放每个字母的个数。

遍历一次s, 通过record[s[i]-'a'],可以准确找到是哪个字母的次数应该增加。

遍历t时,减少对应字母的次数。

最后查看record数组,如果哪个字母的次数不为0,说明两个字符串不是字母异位词。

时间复杂度是O(n),2*O(n).

空间复杂度,只是定义了一个常量大小的辅助数组,所以空间复杂度是O(1)。

bool isAnagram(string s, string t) {
    int record[26] = {0};    //创建记录字母个数的数组 26个字母,分别对应一个索引,0索引处是字母a的个数,25索引处是字母z的个数
    //遍历s,找到某个字母就让记录它的个数+1
    for (int i = 0; i < s.size();i++)
    {
        record[s[i] - 'a']++; 
    }
    for (int j = 0; j < t.size();j++)
    {
        record[t[j] - 'a']--;
    }
    for (int k = 0; k < 26;k++)
    {
        if(record[k]!=0)  //record数组中如果有的元素不是0,说明s和t一定是有谁多了字符或者少了字符。
        {
            return false;
        }
    }
    //如果record全是0,则说明字符串s和t是字母异位词
    return true;
}

2 . No1002. 查找共用字符

力扣题目链接

给你一个字符串数组 words ,请你找出所有在 words 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。

示例 1:

输入:words = ["bella","label","roller"]输出:["e","l","l"]示例 2:

输入:words = ["cool","lock","cook"]输出:["c","o"]

提示:

1 <= words.length <= 1001 <= words[i].length <= 100words[i] 由小写英文字母组成

思路:

计算字符串数组中每个字符串中字母出现的个数。保存每个字母的最小的频数,如果某个字母最小出现的频数是0, 说明在某个字符串中这个字母没有出现过,说明它不是共用字符。如果频数是1,说明是共用字符,每个字符串中它最少出现一次。如果是2,每个字符串最少出现两次。

还是采用具有record[26]来当作哈希表,记录每个字母出现的频数。

vector<string> commonChars(vector<string> &words)
{
    vector<string> result;   //创建结果数组
    int hash[26] = {0};      //hash数组用来存放每个字符串中 每个字符出现的频率,没有出现就是0
                             //hash同时也是用来存放每个字符频率最小值的数组,最后就是需要将他转成string格格式输出
    //用第一个字符串的字符频率来初始化hash数组
    for (int i = 0; i < words[0].size();i++)
    {
        hash[words[0][i] - 'a']++;
    }
    //接下来就是把另外的字符串的字符频率放在另一个数组中
    for (int i = 1; i < words.size();i++)
    {
        int hashother[26] = {0};
        for (int j = 0; j < words[i].size();j++)
        {
            hashother[words[i][j] - 'a']++;
        }
        for (int k = 0; k < 26;k++)
        {
            hash[k] = min(hash[k], hashother[k]);  //每次得到一个字符串中字符的频率,就和现有频率最小值 比较一下,选取最小的放到hash中,
        }
    }
    for(int k = 0; k < 26;k++)
    {
        if(hash[k]!=0)
        {
            while(hash[k]--)    //具有重复的字符,比如这个字符出现的最低频率是2,则每个字符串里都最少出现两次这个字符
            {
                string c = string(1, (char)(k + 97)); // string的一种构造函数,就是string(int n, char a); 强转类型 (char)val;
                result.push_back(c);
            }
        }
    }
    return result;
}
  1. No383. 赎金信

    力扣题目链接

    给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。

    (题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)

    注意:

    你可以假设两个字符串均只含有小写字母。

    canConstruct("a", "b") -> false canConstruct("aa", "ab") -> false canConstruct("aa", "aab") -> true

    思路

    这道题目和242.有效的字母异位词很像,242.有效的字母异位词相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。

    本题判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成,但是这里需要注意两点。

    • 第一点“为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思”  这里说明杂志里面的字母不可重复使用。

    • 第二点 “你可以假设两个字符串均只含有小写字母。” 说明只有小写字母,这一点很重要

    代码如下:

    bool canConstruct(string ransomNote, string magazine)
    {
        int hash[26] = {0};
        for (int i = 0; i < magazine.size();i++) 
        {
            hash[magazine[i] - 'a']++;     // 记录字母出现的次数 加加 
        }
        for (int j = 0; j < ransomNote.size();j++)
        {
            hash[ransomNote[j] - 'a']--;   // 记录字母出现的次数 减减
        }
        for (int k = 0; k < 26;k++)
        {
            if(hash[k]<0)            // 如果记录数出现负值,说明在magazine中没有,在ransomNote中有。所以magazine中的不能构成ransomNote
            {                        // 就应该返回false
                return false;        
            }
        }
        return true;
    }

二、用set来当作哈希表(unordered_set)

  1. No349 两个数组的交集

    力扣题目链接

    题意:给定两个数组,编写一个函数来计算它们的交集。

    说明:输出结果中的每个元素一定是唯一的。我们可以不考虑输出结果的顺序。

    思路:

    这个题由于它不想前面两个是固定的哈希值,这个题的哈希值我们不知道是多好,所以就不能用数组,而需要使用set来进行构造哈希表。通过哈希表,我们可以用set.find()来找到对应元素。

    找两个数组的交集,是需要找到两个数组的公共部分

    首先把一个数组放入一个无序set中,set中的元素是不重复的,这道题不需要有序。然后遍历另一个数组,如果能在这个无序set中,找到另一个数组中的元素,那么这个元素就是两个数组的公共元素了。

    代码如下:

    vector<int> intersection(vector<int> &nums1, vector<int> &nums2)
    {
        unordered_set<int> result_set; //存放结果
        unordered_set<int> nums_set(nums1.begin(), nums1.end()); //unordered_set的拷贝构造函数unordered_set<int> nums_set(st.begin(),st.end())
                                                                 //将st begin()到end()范围的内容,拷贝给nums_set;
        for(int num: nums2)       //范围for循环,num是单个元素,nums2是一个可迭代的序列。
        {                         //如果发现在nums2中的元素在集合中有,那就
            if (nums_set.find(num) != nums_set.end()) //find函数可以用于查找元素。find(key)查找key是否存在,如果存在,返回该元素的迭代器,如果不存在,返回set.end();
            {
                result_set.insert(num);  
            }
        }
        return vector<int>(result_set.begin(), result_set.end());  //vector的拷贝函数。因为最终输出要求一个vector容器
    }

  2. No202 快乐数

力扣题目链接

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

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

如果 n 是快乐数就返回 True ;不是,则返回 False 。

示例:

输入:19 输出:true 解释: 1^2 + 9^2 = 82 8^2 + 2^2 = 68 6^2 + 8^2 = 100 1^2 + 0^2 + 0^2 = 1

思路:

因为每得到一个数,都需要做一个求平方和的操作,所以先封装一个函数求平方和。取出一个正整数的每一位上的数,先膜10,取出最后一位,再除10,去掉最后一位,使倒数第二位成为最后一位,继续进行,直到除10等于0。

本题的解题关键在于,如果不是快乐数一直求每一位平方和,总会出现循环。循环意味着会出现和之前相同的和。所以我们把每一次求和的结果保留下来存进哈希表中。如果出现了和集合中相同的元素,那么就return false。如果出现了1,那就返回true。

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> set;
        while(1) {
            int sum = getSum(n);
            if (sum == 1) {
                return true;
            }
            // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
            if (set.find(sum) != set.end()) {
                return false;
            } else {
                set.insert(sum);
            }
            n = sum;
        }
    }
};

三、用map来当作哈希表(unordered_map)

经典哈希法!!!

当需要对应的两个值的时候,例如同时需要元素的值和下标,就需要map来存放,key是更重要一点的元素,需要通过key进行查找整个map。

  1. No1 两数之和

    力扣题目链接

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

    你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

    示例:

    给定 nums = [2, 7, 11, 15], target = 9

    因为 nums[0] + nums[1] = 2 + 7 = 9

    所以返回 [0, 1]

    思路:

    两数之和的暴力解法就是双重for循环。

    哈希法:

    建立一个map,map的key是元素值,value是这个值在数组中的对应下标。

    遍历数组,遍历nums[0], 在map中找是否存在key == target-nums[0] ,如果不存在,就把这个nums[0]和他的下标,存放在map之中。

    继续遍历数组,遍历到nums[1],在map中找 是否 存在 key == target-nums[1],

    存在对应的key,即可返回这两个数了。

    注意: map的构造和定义! unordered_map<int,int> map 注意是两个int,分别对应的是key 和value 的类型 同时用对组pair创建或者插入的时候,也是

    pair<int,int>(1,2)。 key为1 ,value 为2。

    代码如下:

    vector<int> twoSum(vector<int> & nums, int target)
    {
        unordered_map<int, int> mp;
        for (int i = 0; i < nums.size();i++)  //最多遍历一次数组即可得到结果。
        {
            auto c = mp.find(target-nums[i]);    // 将差值保存一下
            if(c!=mp.end())   // 如果在map的现有key中找到了这个c值,就说明已经找到了这两个数
            {
                vector<int> v = {i, c->second};  
                return v;
            }
            mp.insert(pair<int, int>(nums[i], i));  //如果在map的现有key中没有找到,就把它加入到map中
        }
        return {};
    }
    ​
    //或者这样写也一样的
    vector<int> twoSum(vector<int> & nums, int target)
    {
        vector<int> result;
        unordered_map<int> mp;
        for (int i = 0; i < nums.size();i++)
        {
            if(mp.find(target-nums[i]!=mp.end())
            {
                result.push_back(i);
                result.push_back(mp.find(target - nums[i])->second);
            }
            mp.insert(pair<int>(nums[i], i));
        }
        return result;
    }

  2. No454 四数之和II

力扣题目链接

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。

例如:

输入:A = [ 1, 2]B = [-2,-1]C = [-1, 2]D = [ 0, 2]输出:2

思路:

注意!!!!这道题只需要输出一共有多少个就可以,而不用得到是哪些元组。

将四个数组分成两部分,先使用双层for循环计算a+b的和,将其放进map中,map的key 是a+b的值,value是a+b出现的次数。

这样的话,如果后面找到了可以和a+b匹配为0的c和d,就可以直接统计个数。

利用和两数之和一样的思路,在遍历c和d时,使用target-(c+d)的方式,看现在的map中,是否有a+b可以匹配c+d 等于target。

int fourSumCount(vector<int> &A ,vector<int> &B,vector<int>& C,vector<int> & D)
{
    unordered_map<int, int> umap; // key;a+b的数值,value: a+b数值出现的次数
    //遍历A和B数组,统计两数之和,并把他们都放在map中
    for (int a: A)
    {
        for(int b:B)
        {
            umap[a + b]++;
        }
    }
    //创建int变量count
    int count = 0;
    //遍历C和D数组,如果出现0-(c+d) 在map中的,就把这个value取出来。
    for(int c:C)
    {
        for(int d:D)
        {
            if(umap.find(0-(c+d))!=umap.end())
            {
                count += umap.find(0 - (c + d))->second;
            }
        }
    }
    return count;
}

四、三数之和、四数之和问题(找到不同的元组)(双指针法)

  1. No 15 三数之和

    力扣题目链接

    给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

    注意: 答案中不可以包含重复的三元组。

    示例:

    给定数组 nums = [-1, 0, 1, 2, -1, -4],

    满足要求的三元组集合为:[ [-1, 0, 1], [-1, -1, 2]]

    思路:

    // 这道题用哈希法不太合适,因为去重操作中有很多细节需要注意,再面试中很难写出没有bug的代码

    // 使用双指针法

    // 1. 首先将数组排序,有一层for循环,i从下标0的地方开始,同时定一个下标left定义在i+1的位置,定义下标right在数组结尾的位置

    // 2. 相当于a = nums[i] b = nums[left] c=nums[right] 要找到a+b+c=0

    // 3. 移动left和right的规则:

    // 1. 如果nums[i]+nums[left]+nums[right]>0,由于已经是排序过后的数组了,说明此时三数之和大了,可以将right向左移动一下

    // 2. 如果nums[i]+nums[left]+nums[right]<0,说明此时三数之和小了,left就向右移动,

    // 直到,left和right相遇为止

    // 时间复杂度 O(n2)

    代码如下:

  2. No 18 四数之和

力扣题目链接

题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 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]]

思路:

代码如下:

声明: 本人是个小白。根据代码随想录 Carl哥 的攻略自己总结,思路都是代码随想录中的。

转载其标明原出处。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值