算法训练营 day6 哈希表 有效的字母异位词 两个数组的交集 快乐数 两数之和
哈希表理论基础
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希表是根据关键码的值而直接进行访问的数据结构。
一般哈希表都是用来快速判断一个元素是否出现集合里
当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
例如我们需要初始化把学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。
将学生姓名映射到哈希表上就涉及到了hash function ,也就是哈希函数。
哈希函数
将哈希表中元素的关键键值映射为元素存储位置的函数。
哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
那么如果学生的数量过大,甚至大于了tablesize了。就会出现冲突。此时就是哈希碰撞。
哈希碰撞
如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞。
冲突的处理 一般为两种链接法(拉链法),线性探测法(开放寻址法)。
拉链法
将具有同一散列地址的记录存储在一条线性链表中
线性探测法
插入一个关键字k,如果k被占用,则往后一个位置插入, 直到没有空间。
一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
数组其实是一个简单的哈希表,则我们可以定义一个数组,记录字符串各个字符的出现次数。
因为给出来的都是小写字母,我们不需要记住每个字母的ASCII码,只需要获取字符字符-‘a’即可。所有26个字符最后的值只有可能是在0-25中。所以我们定义一个数组长度为26。即可记录字符串s每个字符出现的次数,再和字符串每个字符出现的情况做比较。
java代码如下:
class Solution {
public boolean isAnagram(String s, String t) {
int[] hash = new int[26];
boolean flag = true;
for (int i = 0; i < s.length(); i++) {
hash[s.charAt(i)-'a']++;
}
for (int i = 0; i < t.length(); i++) {
hash[t.charAt(i)-'a']--;
}
for (int a:hash) {
if (a!=0)
flag=false;
}
return flag;
}
}
两个数组的交集
给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
我们分析题目可知输出的结果的每一个元素是唯一的,也就是输出的结果也是去重,而且不考虑输出结果的顺序。
Set求解
我们可以使用Set来解决问题,首先我们先将第一个数组array1
转换为Set集合set1
,此时set1
中存储的就是出现的元素集合。我们只需要将array2
中的元素遍历查找,将相同的元素存储入另外一个Set集合set2
即可。
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
HashSet<Integer> result = new HashSet<Integer>();
HashSet<Integer> values = new HashSet<Integer>();
for (int s1: nums1) {
result.add(s1);
}
for (int s2: nums2) {
if (result.contains(s2))
values.add(s2);
}
int[] toArray = new int[values.size()];
int index = 0;
for (int i:values){
toArray[index++]=i;
}
return toArray;
}
}
数组求解
在前不久 leetcode 修改了题目描述和后台数据。将数据的长度减少到了1000。
所以这一题也可以按照数组来解决
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
HashSet<Integer> integers = new HashSet<>();
int[] hash = new int[1001];
int index = 0;
for (int i = 0; i < nums1.length; i++) {
hash[nums1[i]] = 1;
}
for (int i = 0; i < nums2.length; i++) {
if (hash[nums2[i]] == 1) {
integers.add(nums2[i]);
}
}
int[] toarray = new int[integers.size()];
for (int a : integers) {
toarray[index++] = a;
}
return toarray;
}
}
快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
分析题目我们可以知道经过多次计算后只有两种情况,1无限循环,返回false。2结果为1,返回true。
这个题目的关键点就在于会进入无限循环,则我们可以将每一次计算的结果放入Set集合中,之后的每次计算结果与Set集合比较,如果检测到有相同的值则直接返回false。
class Solution {
public boolean isHappy(int n) {
boolean flag ;
HashSet<Integer> set = new HashSet<>();
int sum = 0;
while (true) {
sum=getNextNumber(n);
if (n==1){
return true;
}
if (set.contains(sum)){
return false;
}else{
set.add(sum);
}
n=sum;
}
}
public static int getNextNumber(int n) {
int res = 0;
while (n > 0) {
int temp = n % 10;
res += temp * temp;
n = n / 10;
}
return res;
}
}
两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
本题呢,需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。
我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。
那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下表}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
代码如下:
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
int[] result = new int[2];
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target-nums[i])){
result[0]=map.get(target-nums[i]);
result[1]=i;
}
map.put(nums[i],i);
}
return result;
}
}