代码随想录算法训练营day6 | 242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和

哈希表概述
哈希表是根据key值直接进行访问的数据结构,一般用于快速查询一个元素(key值)是否出现过,是否在一个集合中,查询时间复杂度为O(1)。

常见的三种用于作哈希表的数据结构:
1、数组
本质上存放的也是键值对,只不过key值是数组下标

一般使用数组来做哈希的题目,是因为题目都限制了数值的大小,如:
1 <= nums1.length, nums2.length <= 1000 //说明value值范围为1-1000,使用int型数组即可
0 <= nums1[i], nums2[i] <= 1000 //说明key值范围为0-1000,使用长度为1000的数组即可

如果题目没有限制数值的大小,就无法使用数组来做哈希表了。

如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。

2、set(集合或key表,只存放key值)

std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。

当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。

unordered_set常见接口:

点击查看代码
#include <iostream>
#include <unordered_set>

//创建空的unordered_set
unordered_set<int> uoset;

//插入元素 两种方法 emplace性能更优
uoset.insert(10);
uoset.emplace(20);

//查找元素,查看成功则返回对应的迭代器,查找失败则返回uoset.end()迭代器
auto iter = uoset.find(10);

//删除元素
uoset.erase(10);

//拷贝构造unordered_set,若有vector容器,可用vector里的元素初始化unordered_set,如:
vector<int> v(10,1);
unordered_set<int> uoset(v.begin(), v.end());

3.map(键值对表,表中元素时一个个的键值对pair,即<key,value>)

如果要存放的不是一个一个的值,而是一对一对的值,则要选用map

unordered_map常见接口:

点击查看代码
unordered_map<int, string> uomap;

uomap.insert({1, "Apple"});

 //如果该key存在,则返回对应的value,如果不存在,会插入该key,并默认初始化value,并返回该value的引用类型(可作为左值)
uomap[key]
uomap[2] = "banana" //插入键值对
uomap[2] = "orange" //修改key2对应的value

auto iter = uomap.find(1) //根据key值查找,查找成功返回对应的迭代器,失败则返回uomap.end()迭代器

it->first //访问key
it->second //访问value

uomap.erase(1);

242.有效的字母异位词
使用数组作为哈希表

点击查看代码
class Solution {
public:
    bool isAnagram(string s, string t) {
        if(s.size() != t.size()) return 0;
        int hashMap[26] = {0}; //用数组作为哈希表
        for(int i = 0; i < s.size(); ++i) {
            //s[i] - 'a'作为key值,由于是26个小写字母,相当于限制了key值只有
            //26个,且为连续的26个,故可使用数组作为哈希表,数组长度设为26
            ++hashMap[s[i] - 'a'];
            --hashMap[t[i] - 'a'];
        }
        for(int i = 0; i < 26; ++i) {
            if(hashMap[i] != 0) return 0;
        }
        return 1;
    }
};

出现的一次错误:
本题中,第一次写将以下代码中的i < 26错误地写成了i < s.size()
for(int i = 0; i < 26; ++i) {
if(hashMap[i] != 0) return 0;
}
导致以下用例不通过:s = "ggii" t = "eekk"
Debug过程:
在循环体内添加打印代码:
for(int i = 0; i < s.size(); ++i) {
if(hashMap[i] != 0) return 0;
cout << hashMap[i] << endl;
}
发现输出了4个0后就返回了true,即循环体内只执行了4次,而正常应该检查hashmap[26]中的26个值是否均为0,循环体应该执行26次,故检查循环条件是否设置异常?
结果发现循环体错误地写成了i < s.size(),s为"ggii",正好size为4,故打印了4个0,正确的循环条件应该是i < 26,debug完毕!

349.两个数组的交集
set作为哈希表

点击查看代码
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> uoset1;
        for(int i = 0; i < nums1.size(); ++i) {
            uoset1.insert(nums1[i]);
        }
        unordered_set<int> result_set;
        for(int i = 0; i < nums2.size(); ++i) {
            if(uoset1.find(nums2[i]) != uoset1.end()) {
                result_set.insert(nums2[i]);
            }
        }
        vector<int> result_vec(result_set.begin(), result_set.end());
        return result_vec;
    }
};

核心思想:
本质上是要检查nums2中的数字是否在nums1出现过,出现过则输出到交集中,故应考虑使用哈希表。
本题中,nums1中各数字出现几次并不重要,在哪个数组下标出现的也不重要,大小顺序也不重要,故只需将各数字作为key值保存起来,应当采用unordered_set存放nums1的key值。然后遍历nums2中的各数字在nums1是否出现过,出现过则输出到交集中,由于不考虑输出结果的顺序以及要求去重,故结果交集也应当使用unordered_set先存放,返回结果前再拷贝到vector中进行返回。

由于本题限制了nums[i]的取值范围,故选用数组作为哈希表亦可:
0 <= nums1[i], nums2[i] <= 1000 //说明key值范围为0-1000,使用长度为1000的数组即可

202.快乐数
set作为哈希表

点击查看代码
class Solution {
public:
    bool isHappy(int n) {
        unordered_set<int> uoset;
        int sum = 0;
        while(1) {
            while(n != 0) {
                sum += (n % 10) * (n % 10);
                n /= 10;
            }
            if(sum == 1) return 1;
            if(uoset.find(sum) != uoset.end()) return 0;
            uoset.insert(sum);
            n = sum;
            sum = 0;
        }
    }
};

本题的关键在于,如果一个数不是快乐数,则会出现无限循环(注意不是死循环),即某一次算出的和值sum在之前出现过,故应该使用哈希表判断每次算出的sum值在之前是否出现过。由于sum值不固定,范围很大,且只需将sum值作为key值,无需value值,故选择unordered_set作为哈希表。

1.两数之和
map作为哈希表

点击查看代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> result;
        unordered_map<int, int> uomap;
        for(int i = 0; i < nums.size(); ++i) {
            if(uomap.find(target - nums[i]) != uomap.end()) {
                result.push_back(i);
                result.push_back(uomap[target - nums[i]]);
                return result;
            }
             //若查找不到,则将当前key值和数组下标插入哈希表
            uomap[nums[i]] = i;
        }
        return result;
    }
};

核心思想:
当遍历到nums[i]时,想查询target - nums[i]是否在数组中,则考虑使用哈希表,本题中不仅要知道target - nums[i]是否在数组中,如果在数组中,还需要知道其在数组的下标,故哈希表存放的应该是一对一对的pair,所以选择unordered_map作为哈希表,需要查询的是target - nums[i]在不在,所以target - nums[i]作为key,其对应的数组下标作为value。如果查找到了,则将当前数组元素下标和根据key查询到的value值返回,如果查找不到,则将当前数组元素和其对应的下标分别作为key和value存入哈希表中,以便后续查询。

2025/02/18

posted @   coder小杰  阅读(157)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示