代码随想录算法训练营第六天 | 哈希法

哈希表理论基础

文章讲解:代码随想录 (programmercarl.com)

什么时候想到用哈希表

判断某个元素是否存在于集合中时,就要考虑用哈希表。遍历需要O(n),而哈希表只需要O(1)。哈希法以空间换时间

定义

哈希表是根据关键码直接进行访问的数据结构。eg. 数组就是哈希表,数组的下标就是哈希表的关键码,通过下标访问数组中的元素。

哈希表中的关键码可以是int,double,string等等。eg. 要查询一个名字是否在学校里,只需要初始化把所有名字都存在哈希表里(可以用multiset来实现),在查询时将某个名字作为索引就可以知道该名字是否在学校。

关于“哈希表”、“哈希函数”、“哈希碰撞”、“拉链法“、”线性探测法“的详细信息可参见文章讲解。

常见的三种哈希结构

一般使用 数组、set(集合)、map(映射) 来实现哈希法。

1、数组作为哈希表时,由于其大小可不是无限开辟的,【只有当题目给出固定大小的范围且不浪费大量存储空间时才可使用】。

2、在C++中,set的底层实现以及优劣如下表所示,【set是一个集合,里面放的元素只能是一个key

  • 使用set实现哈希法时,优先使用std::unordered_set,因为查询和增删效率最优;
  • 若需要集合是有序的且无重复数值,则使用std::set;
  • 若需要集合是有序的且有重复数值,则使用std::multiset。
集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::unordered_set哈希表无序O(1)O(1)
std::set红黑树有序O(log n)O(log n)
std::multiset红黑树有序O(logn)O(logn)

3、在C++中,map的底层实现以及优劣如下表所示,

【map是一种key value的存储结构,可以用key保存数值,用value来保存数值key所对应的数据(比如下标)

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

242 数组作为哈希表

讲解视频:学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词_哔哩哔哩_bilibili

文章讲解:代码随想录 (programmercarl.com)

状态:不看题解可以想出,不过不知道这就是哈希法。建议记录作为“数组作为哈希表”的例题。

解题代码如下

bool isAnagram(string s, string t) {
	//创建长度为26的int数组records,records[0]表示字母'a'出现的次数,records[1]表示字母'b'出现的次数,以此类推。
	int records[26] = { 0 };
	//遍历s,将各个字母出现的次数记录到records
	for (int i = 0; i < s.size(); ++i) {
		++records[s[i] - 'a'];
	}
	//遍历t,若某个字母出现一次,则将其从records中减去一次。
	for (int i = 0; i < t.size(); ++i) {
		--records[t[i] - 'a'];
	}
	//遍历records,若每个字母代表的次数都是0,说明s和t中每个字符出现的次数都相同;否则,则不同。
	for (int i = 0; i < 26; ++i) {
		if (records[i] != 0) {
			return false;
		}
	}
	return true;
}

349 集合set作为哈希表

讲解视频:学透哈希表,set使用有技巧!Leetcode:349. 两个数组的交集_哔哩哔哩_bilibili

文章讲解:代码随想录 (programmercarl.com)

状态:这道题做之前看了题解,记录作为“集合set作为哈希表”的例题。做本题时,不会使用STL的set的方法,因此属于直接抄题解。代码中的STL的使用方法值得记录,做题时可用。

这道题之前没有限制数值大小,故无法用数组来实现哈希法,且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费,因此采用set来实现哈希法。现在限制了数组大小,既可以采用set,也可以采用数组。不过这里还是使用set来实现。

题意说明“输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。”,所以采用unordered_set作为哈希表。

思路

在这里插入图片描述

  1. 先将数组nums1存到名为nums_set的哈希表(unordered_set)中,
  2. 然后遍历数组nums2,
    1. 若nums2的元素在nums_set中出现过,说明是相交的元素,将其存到名为result_set的哈希表(unordered_set)中。
  3. 将结果哈希表result_set转为vector形式。

代码

vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    unordered_set<int> result_set;  //存放结果,之所以用set是为了给结果集去重
    unordered_set<int> nums_set(nums1.begin(), nums1.end());    //将数组nums1存到名为nums_set的哈希表
    for(int num : nums2){   //遍历数组nums2
        if(nums_set.find(num) != nums_set.end()){   //若nums2的元素在nums_set中出现过
            result_set.insert(num);     //将其存到名为result_set的哈希表
        }
    }
    return vector<int>(result_set.begin(), result_set.end());   //将结果哈希表result_set转为vector形式
}

202 集合set作为哈希表

讲解视频:无

文章讲解:代码随想录 (programmercarl.com)

状态:这道题做之前看了题解,记录作为“集合set作为哈希表”的例题。

思路

题目说"无限循环 但始终变不到 1"是false的情况,“无限循环”也就是这个值会重复出现,那么就可以用哈希表来判断之前是否出现过

在while(1)循环中,

  1. 求数n每个位置上的数字的平方和,存到sum;
  2. 若sum==1,则return true;
  3. 若sum在哈希表set中,则return false;
  4. 若sum不在哈希表中,则将其插入哈希表;
  5. 令n=sum。

由于要求每个元素唯一且不要求有序,故哈希表采用unordered_set。

代码

class Solution {
public:
    int getSum(int n) { //求数的各个位置上的数字的平方和
        int sum = 0;
        while (n != 0) {
            sum += (n % 10)*(n % 10);
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n) {
        unordered_set<int> set;
        while(1){
            int sum = getSum(n);    //求数n每个位置上的数字的平方和
            if(sum == 1)return true;
            if(set.find(sum) != set.end()) return false;    //sum在哈希表set
            else set.insert(sum);   //若sum不在哈希表中,则将其插入哈希表
            n = sum;
        }
    }
};

1 map作为哈希表

讲解视频:梦开始的地方,Leetcode:1.两数之和,学透哈希表,map使用有技巧!_哔哩哔哩_bilibili

文章讲解:代码随想录 (programmercarl.com)

状态:思路想出来了,没想到用map作为哈希表。记录作为“集合map作为哈希表”的例题。做本题时,不会使用STL的map的方法,因此属于直接抄题解。代码中的STL的使用方法值得记录,做题时可用。

思路

  • 本题呢,我需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。那么我们就应该想到使用哈希法了。因为本题,我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适

  • 这道题目中并不需要key有序,选择std::unordered_map 效率更高!

  • map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)。这道题我们需要给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。

  • 在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。过程如下图所示,

    在这里插入图片描述
    在这里插入图片描述

代码

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> map;
        for(int i = 0; i < nums.size(); ++i){   //遍历数组
            auto iter = map.find(target - nums[i]); //向map去查询是否有和目前遍历元素匹配的数值
            if(iter != map.end()){  //如果有
                return {iter->second, i};   //就找到的匹配对
            }
            else{   //如果没有
                map.insert(pair<int, int>(nums[i], i));     //就把目前遍历的元素放进map中
            }
        } 
        return {};
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值