代码随想录训练营第六天| 哈希表理论基础 ● 242.有效的字母异位词 ● 349. 两个数组的交集 ● 202. 快乐数● 1. 两数之和

哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。什么时候想到用哈希法,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。  这句话很重要,大家在做哈希表题目都要思考这句话。 

实现哈希表一般有三种方式:数组、set和map,在这里我先再介绍一下set和map的各种基本使用方法。

Set

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

一、set基本概念

set 的含义是集合,它是一个有序的容器,里面的元素都是排序好的,支持插入,删除,查找等操作,就像一个集合一样。所有的操作的都是严格在logn时间之内完成,效率非常高。set 和 multiset 的区别是:set 插入的元素不能相同,但是 multiset 可以相同。其特点如下:

1、每个元素的键值都唯一,不允许两个元素有相同的键值。
2、所有元素都会根据元素的键值自动排序(默认从小到大)。
3、set 中的元素不像 map 那样可以同时拥有实值(value)和键值(key),只能存储键,是单纯的键的集合。
4、set 中元素的值不能直接被改变。
5、set 支持大部分的map的操作,但是 set 不支持下标的操作,而且没有定义mapped_type类型。

二、set基本操作

使用STL标准库的 set 时,应包含头文件:#include <set>

1、set初始化
set<Type> s						      //定义一个set容器
set<Type> s(s1)			              //定义一个set容器,并用容器s1来初始化
set<Type> s(b, e)					  //b和e分别为迭代器的开始和结束的标记
set<Type> s(s1.begin(), s1.begin()+3) //用容器s1的第0个到第2个值初始化s
set<Type> s(a, a + 5)      		      //将a数组的元素初始化vec向量,不包括a[4]
set<Type> s(&a[1], &a[4]) 			  //将a[1]~a[4]范围内的元素作为s的初始值
2、set 的基本操作
s.begin()					//返回指向第一个元素的迭代器
s.end()						//返回指向最后一个元素的迭代器
s.clear()					//清除所有元素
s.count()					//返回某个值元素的个数
s.empty()					//如果集合为空,返回true,否则返回false
s.equal_range()				//返回集合中与给定值相等的上下限的两个迭代器
s.erase()					//删除集合中的元素
s.find(k)					//返回一个指向被查找到元素的迭代器
s.insert()					//在集合中插入元素
s.lower_bound(k)			//返回一个迭代器,指向键值大于等于k的第一个元素
s.upper_bound(k)			//返回一个迭代器,指向键值大于k的第一个元素
s.max_size()				//返回集合能容纳的元素的最大限值
s.rbegin()					//返回指向集合中最后一个元素的反向迭代器
s.rend()					//返回指向集合中第一个元素的反向迭代器
s.size()					//集合中元素的数目
3、set详细操作

可参考这篇博客,演示的十分清晰:【总结】C++ 基础数据结构 —— STL之集合(set)用法详解_c++ stl set-CSDN博客

Map

std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。

具体细节和用法参考这篇博客:http://t.csdnimg.cn/OumRE

Set与Map的区别:

(1)map中的元素是key-value(关键字—值)对:关键字起到索引的作用,值则表示与索引相关联的数据;Set与之相对就是关键字的简单集合,set中每个元素只包含一个关键字。

(2)set的迭代器是const的,不允许修改元素的值;map允许修改value,但不允许修改key。其原因是因为map和set是根据关键字排序来保证其有序性的,如果允许修改key的话,那么首先需要删除该键,然后调节平衡,再插入修改后的键值,调节平衡,如此一来,严重破坏了map和set的结构,导致iterator失效,不知道应该指向改变前的位置,还是指向改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值;而map的迭代器则不允许修改key值,允许修改value值。
(3)map支持下标操作,set不支持下标操作。map可以用key做下标,map的下标运算符[ ]将关键码作为下标去执行查找,如果关键码不存在,则插入一个具有该关键码和mapped_type类型默认值的元素至map中,因此下标运算符[ ]在map应用中需要慎用,const_map不能用,只希望确定某一个关键值是否存在而不希望插入元素时也不应该使用,mapped_type类型没有默认值也不应该使用。如果find能解决需要,尽可能用find。

242.有效的字母异位词

题目链接/文章讲解/视频讲解:  代码随想录
bool isAnagram(string s, string t) {
        int record[26] = {0};
        for (int i = 0; i < s.size(); i++) {
            // 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
            record[s[i] - 'a']++;
        }
        for (int i = 0; i < t.size(); i++) {
            record[t[i] - 'a']--;
        }
        for (int i = 0; i < 26; i++) {
            if (record[i] != 0) {
                // record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
                return false;
            }
        }
        // record数组所有元素都为零0,说明字符串s和t是字母异位词
        return true;
    }

 思路:使用数组实现哈希表,创建一个26位大小的数组,先遍历第一个字符串,将每个字母所在的索引下标数组加一,再遍历第二个数组,将每个字母的索引下标数组减一。遍历过后如果数组中元素全部为0,则说明组成这两个字符串的字母全部相同。

349. 两个数组的交集

题目链接/文章讲解/视频讲解: 代码随想录
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        for (int num : nums2) {
            // 发现nums2的元素 在nums_set里又出现过
            if (nums_set.find(num) != nums_set.end()) {
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }

 这道题使用set进行去重操作,std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。同时,nums_set.find(num)函数是直接通过set中的元素去寻找其所在位置,若不存在则会返回至nums_set.end()(set.end()指的是set中最后一个元素再后一个的迭代器,标志着set的结束,并不会记录数据!),通过这个函数来判断在nums2中出现的元素是否在nums1当中,这样能快速找到数组的交集。

同时,由于该题目有这个条件:0 <= nums1[i], nums2[i] <= 1000,也可以采用和上道题相同的方法,开一个1001大小的数组进行哈希表的建立。

202. 快乐数

题目链接/文章讲解: 代码随想录
class Solution {
public:
    int getSum(int n) {     //计算每个位置的平方和
            int sum = 0;
            int i = 0;
            while (n!=0) {
                i = n%10;
                n /= 10;
                sum += i*i;
            }
            return sum;
        }
    bool isHappy(int n) {
        unordered_set<int> set;
        while(1) {
            n = getSum(n);
            if (n == 1)
                break;
            else if (set.find(n) == set.end())
                set.insert(n);
            else
                return false;
        }
        return true;
    }
};

 这道题使用getSum()函数来求数字的平方和,然后由于题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!如果在循环的过程中出现了重复的和,那么说明这个循环会一直重复下去,也就是永远也到达不了1,因此应该返回false。使用和上一道题相同的方法来进行查重:用set.find(n) == set.end()来判断新的sum在原来的set中出现过吗,若没出现过且不等于一,则将其插入进去;若等于1,则符合条件返回true;若出现过,则说明会一直循环下去,直接返回false。

1. 两数之和 

题目链接/文章讲解/视频讲解: 代码随想录
vector<int> twoSum(vector<int>& nums, int target) {
        std::unordered_map <int,int> map;
        for (int i = 0; i < nums.size(); i++) {
            if (map.find(target - nums[i]) != map.end()) {
                return {map.find(target - nums[i])->second, i};
            }
            else {
                map.insert(std::pair<int, int>(nums[i], i));
            }
        }
        return {};
    }

因为需要返回的是元素在数组中的下标,所以要使用map来进行记录数组中元素的值和下标。在遍历中判断是否有满足条件的值已经被存入map,如果已经被存入了,则返回这两个元素的下标,否则将这个元素存入map进行下一轮的匹配。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值