算法第5天|哈希表基础理论总结、有效的字母异位词LeetCode242、两个数组的交集LeetCode349、快乐数LeetCode202、两数之和LeetCode1

今日整体问题总结:

1、在使用map中要注意find(x)查询的是键,而不是值

2、要注意多使用迭代器来解决问题,而不是总是使用下标,要知道set、map常用的一些函数,便于简化计算。

3、当判断一个值是不是出现过,要注意使用哈希表(数组、map、set要注意使用场合)

哈希希表(散列表,hash table)基础理论总结

简单理解:

        哈希表就是一个数组,通过数组的下标索引访问数组中的元素

哈希表作用:

        1、将一个庞大的值域映射到一个比较小的空间。

        2、判断一个元素是否 出现在集合中(哈希表的重要作用)

        所以使用哈希表时一般是对数据进行插入、查询,删除一般是通过另开一个数组,对冲突值打上标记,不去遍历它。

哈希函数

将大值域的数映射到小值域的函数,通过取模运算,确保每个值在哈希表中有对应的位置索引(但是当数据量>哈希函数的取模时,会导致哈希表中的一个索引位置有多个值,产生哈希碰撞

        哈希函数在进行取模时,尽量选择质数,离2的幂次尽量的远

算法第5天|哈希表基础理论总结、有效的字母异位词LeetCode242、两个数组的交集LeetCode349、快乐数LeetCode202、两数之和LeetCode1_第1张图片

 解决哈希碰撞的方法有两种:

        1、拉链法:将映射到同一个索引下的元素通过链表存储起来

//哈希表拉链法:
#include 
#include 
#include 
using namespace std;

const int N = 100003;//取模选择质数

int h [N];//定义一个槽,在每个槽中定义一个链(存值、存下一个位置坐标,当前用到哪些位置)
int e[N],ne[N],idx;//e[N]表示当前节点的val,ne[N]表示当前节点的next指针,idx表示用到哪个唯一索引


//插入函数,在某个槽上进行头节点插入(单链表)
void insert(int x)
{
    //定义一个哈希函数
    int k = (x%N+N)%N;//有负值
    //插入操作就是将值插入到h[k]上,链表头插
    e[idx] = x;//将当前的值存入到链表中
    ne[idx] = h[k]; //将当前节点的下一位置的指针指向h[k];
    h[k] = idx++;
}

bool find (int x)
{
    int k = (x%N+N)%N;//哈希函数,获取元素在哈希表中的具体槽位置
    //遍历该槽上所有的节点
    for(int i=h[k];i!=-1;i=ne[i])
    {
        if(e[i] == x)
        return true;
        else
        return false;
    }
}

        2、开放寻址法:定义一个较大的数组,判断元素通过哈希函数后的数据在哈希表中的相应位置是不是存在,如果不存在直接插入,存在且不为当前元素,就往后 开始判断,直到数组最后,再从哈希表的0开始,直到找到一个空位置,将其插入。

/*
 *哈希表的开放寻址法:也是在小值域上产生冲突时使用 
    *使用了一维数组,但是一维数组的长度是题目要求的2~3倍
 * 
*/
//假设题目要求的n为100000,所以定义N为200000,在N后边寻找一个质数赋给N

#include 
#include 
using namespace std;

const int N=2000003;
const int null =0x3f3f3f3f;//定义一个标志,表示数组是空,当数组是这个值的时,就是没有值所占用
int h[N];

//开放寻址法的关键函数find(),如果x在哈希表中已经存在,则返回存在的位置;如果不存在,则返回要存储的位置
int find(int x)
{
    //定义在该值域下的对应哈希数值
    int k = (x%N+N)%N;
    while(h[k]!=null&&h[k]!=x)//有人且不等于x
    {
        k++;
        if(k ==N) k=0;//如果到最后了,循环回去 
    }
    return k;
}

 常见的哈希结构

        1、数组(上边的两段程序就是模拟哈希表)

        2、set(集合)

集合 底层实现 是否有序 数值是否可以重复 能否更改数值  查询效率  增删效率
set 红黑树 有序 不可重复 不可 O(log n) O(log n)
multiset 红黑树 有序 可重复 不可 Olog(n) O(log n)
unordered_set 哈希表 无序 不可重复 不可 O(1)

O(1)

                如果在使用set集合解决哈希问题的时候:

                1、优先使用unordered_set,因为它的 查询增删的效率是O(1),其他的都是O(log n);

                2、如果要求有序,使用set;

                3、如果有重复元素,使用multiset;

        3、map(映射)

映射 底层实现 是否有序 数值是否可以重复 能否更改数值  查询效率  增删效率
map 红黑树 key有序 key不可重复 key不可 O(log n) O(log n)
multimap 红黑树 key有序 key可重复 key不可 Olog(n) O(log n)
unordered_map 哈希表 key无序 key不可重复 key不可 O(1) O(1)

                map中key是通过红黑树或者哈希表创建的,所以对key有规定,但是对value没有

                如果在使用map解决哈希问题的时候:

                1、优先使用unordered_map,因为它的查询增删效率是O(1),其他的都是O(log n);

                2、如果需要key是有序的,那么就用map;

                3、如果要求key不仅有序还要有重复数据的话,那么就用multimap。

容器set和容器map

容器set介绍

#include 中包含set和multiset两个容器,分别为有序集合和有序多重集合;#include中包含unordered_set容器,为无序集合。

set是有序集合,但是输入可以是无序的,自动排列成有序存储

set的本质:动态的维护一个有序集合

set的声明: set <数据类型> 变量名称

set常用函数:与vector容器类似可以使用sizeemptyclear

set迭代器:

        set和multiset的迭代器称为“双向迭代器”,不像vector迭代器一样支持“随机访问”,支持星号“*”解引用支持“++”“--”两种操作

        迭代器的定义方式: set:: iterator it;,将it++,it会指向下一个元素。

        begin()/end()迭代器:

                返回集合的首尾迭代器,时间复杂度为O(1)

                s.begin()是指向集合中最小的元素的迭代器

                s.end()是指向集合中最大的元素的下一个位置的迭代器

        insert

                s.insert(x)把一个元素x插入到集合s中,时间复杂度为O(log n)

                在集合中如果已经存在x元素,则不会重复插入该元素,对集合状态无影响

        find

                s.find(x)在集合s中查找等于x的元素,并返回指向该元素的迭代器,若不存在,则返回s.end(),时间复杂度为O(log n)

        lower_bound/upper_bound(unordered_set没有这个操作

                也是查找函数,与find类似,但查找条件不同,时间复杂度为O(log n)

                s.lower_bound(x)查找大于等于x的元素中最小的一个,并返回指向的迭代器

                s.upper_bound(x)查找大于x的元素中最小的一个,并返回指向的迭代器。

        erase

                设it是一个迭代器,s.erase(it)表示从s中删除迭代器it指向的元素,时间复杂度为O(log n)

                设x是一个元素,s.erase(x)表示从s中删除迭代器等于x的元素,时间复杂度为O(k+log n)

        count

                s.count(x)表示返回集合s中等于x的元素个数,时间复杂度为O(k+log n),k表示x的个数

容器map介绍

#include头文件中包含了map容器,map容器是一个键值对key-value的映射,其内部实现是一棵以key为关键码的红黑树。

map的作用:动态的维护有序数列

map的键值对:map的key和value可以是任意类型,其中key必须定义小于号运算符,因为在比较的时候比较的是key的值。

map的声明:map name;

set常用函数:

        size/empty/clear/begin/end均与set类似

        insert/erase

            与set类似,但是其参数是pair

        find

            h.find(x)在变量名为h的map中查找key为x的二元组

        []操作符

                h[key]返回key映射的value的引用,时间复杂度为O(logn)

               []操作符是map最吸引人的地方,可以很方便的通过h[key]来得到key对应的value,和可以对h[key]进行赋值操作,改变key对应的value

有效的字母异位词

题目链接:LeetCode242、有效的字母异位词

总体思路:

        使用一个数组表示哈希表,哈希函数是将对应的小写字母转换成数组的26个下标

        哈希表记录字母出现的次数

        在另一个字符中,减去哈希表对应位置的次数,判断最后哈希表是不是全部为0

class Solution {
public:
    bool isAnagram(string s, string t) {
        //使用一个数组存储对应的26字母出现的次数,判断另一个字符是否相同
        //类似于使用数组表示哈希表,哈希函数是将对应的小写字母转换成对应的数字下标,哈希表中存储的是出现的次数
        int  num[26];
        for(auto a:s)
        {
            num[a-'a']++;
        }
        for(auto i : t)
        {
            num[i-'a']--;
        }
        for(int i=0;i

两个数组的交集

题目链接:两个数组的交集

思路1:使用unordered_set进行判断:

将一个数组输入到无序集合中,对另一个数组遍历,判断无序集合中是否存在当前元素,如果存在,就输入到结果中

        新知识:在创建unordered_set时,

                1、可以使用vector数组的迭代器将其初始化,

                2、也可以在创建之后,通过insert插入元素

//使用vector数组的迭代器初始化
unordered_set  nums(nums1.begin(),nums1.end());

//使用insert插入
unordered_set  nums

for(auto x :nums1)
{
    nums.insert(x);
}

         新知识:在将集合转换成vector时,可以通过迭代器直接转换

//res是unordered_set类型 
vector(res.begin(),res.end())
class Solution {
public:
    vector intersection(vector& nums1, vector& nums2) {
        //判断交集,可以使用unordered_set进行判断,将一个数组输入进集合,对另一个数组进行find,
        //find发现了,就记录,find没发现就继续

        //第一步创建2个unordered_set,一个记录nus1中的元素,一个记录结果
        unordered_set  nums(nums1.begin(),nums1.end());
        unordered_set  res;
        //开始判断数组2中的元素是不是在无序集合中
        for(auto x :nums2)
        {
            if(nums.find(x)!=nums.end())
            {
                //添加到一个列表
                res.insert(x);
            }
        }
        return vector(res.begin(),res.end());
        
    }
};

思路2:使用数组模拟哈希表

        将数组1输入到哈希表,将该位置的哈希表设置为1,表示存在值,

        遍历判断数组2的元素所在哈希表是否有值。

class Solution {
public:
    vector intersection(vector& nums1, vector& nums2) {
        int hash[1003];
        //将数组1输入到哈希表中,等于1表示此处有值
        for(auto i:nums1)
        {
            hash[i] =1;
        }
        //判断数组2与数组1的合集,定义一个无序集合存储结果
        unordered_set  res;
        for(auto i:nums2)
        {
            if(hash[i]==1)
            res.insert(i);
        }
        return vector(res.begin(),res.end());

        
    }
};

快乐数

题目链接:LeetCode202、快乐数

整体思路

        使用无序集合记录出现的数,当出现一样的,表示进入循环,不是快乐数

class Solution {
public:
        
        int sum(int &n)
        {
            int res_sum=0;
            while(n!=0)
            {
                res_sum +=(n%10)*(n%10);
                n/=10;
            }
            return res_sum;
        }
    bool isHappy(int n) {
        //定义一个小函数,计算每位数字平方和结果
        
        //因为可能出现无限循环,定义一个无序集合unordered_set记录当前出现过的数据
        unordered_set  num ;
        while(n!=1)
        {
            if(num.find(n)!=num.end())return false;
            else
            {
                num.insert(n);
                
            }
            n = sum(n);
            
        }
        return true;
        
    }
};

两数之和

题目链接:LeetCode1、两数之和

思路1

        因为要返回的索引,所以可以使用map来记录元素与索引

        遍历数组,使数组中的元素寻找与map中相对应的target-nums[i]是否存在,如果存在就是找到了,如果不存在,就将当前的元素加入到map,继续下一个元素。

class Solution {
public:
    vector twoSum(vector& nums, int target) {
        //使用一个无序映射unordered_map,记录已经遍历过的数组中的数
        //通过遍历数组的当前元素,插叙map中target-nums[i]的结果是否存在
        //因为在map中first代表key,使用find(x),查询的是key是否存在,所以需要将元素值放到first,下标索引放到second
        unordered_map  map;
        vectorres(2);
        //遍历数组,将遍历过的元素存入map
        for(int i=0;i

思路二:双指针 

通过对数组进行排序,使用左右双指针找到对应的值,在原数组中找到两个值的下标。(思路,没实现)


 

你可能感兴趣的:(算法,散列表,哈希算法)