哈希表理论基础
总结:
- 当我们遇到了要快速判断一个元素是否出现在集合里或者重复出现时,就要考虑哈希法。
- 哈希法是用空间换时间的方法,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
更多有关哈希表的理论基础可查阅:《代码随想录》哈希表理论基础
242.有效的字母异位词
题目详细:LeetCode.242
当两个字符串具有以下特点时,则称它们互为字母异位词:
- 字符串的长度相等
- 字符串中的每个字母的出现次数相同
所以我们只需要证明两个字符串是否满足以上两个特点,则可以确认两个字符串是否为字母异位词。
由此可得,我们可以利用哈希表来解决:
- 比较两个字符串的长度,相同则开始统计每个字母的出现次数
- 先统计一个字符串中出现的字母和字母出现的次数
- 再遍历另一个字符串,当遇到相同的字母则其出现的次数减1
- 最后遍历一次统计的结果,如果字母的出现次数为0,则说明在两个字符串中该字母出现的次数相同
哈希表有三种不同的结构,分别是:数组、哈希Set和哈希Map。
这道题我一开始使用的是哈希表中的Map结构来统计字符串中出现的字母和字母出现的次数,但是由于一些特殊的测试用最后还需要再遍历一次表来判断字符串中的每个字母的出现次数是否相同,所以换用数组来进行记录和遍历会时代码更加简洁一些。
Java解法(数组):
class Solution {
public boolean isAnagram(String s, String t) {
int[] set = new int[26];
for(char c: s.toCharArray()){
set[c - 'a']++;
}
for(char c: t.toCharArray()){
int f = --set[c - 'a'];
if(f < 0){
return false;
}
}
for(int n: set){
if(n != 0){
return false;
}
}
return true;
}
}
349. 两个数组的交集
题目详细:LeetCode.349
判断数组的交集十分简单,这里就不多解释了。
Java解法(HashSet):
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> set = new HashSet<>();
Set<Integer> set_ans = new HashSet<>();
for(int n : nums1){
set.add(n);
}
for(int n : nums2){
if(set.contains(n))
set_ans.add(n);
}
return set_ans.stream().mapToInt(Integer::intValue).toArray();
}
}
202. 快乐数
题目详细:LeetCode.202
根据题目描述以及题目给出的示例:
输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
所以,计算数值是否为快乐数有两个需要解决的关键问题:
- 如何计算出数值的按位平方和
- 在每一次计算和替换数值的过程中,如何判断该数值为快乐数
- 如何判断这一过程是无限循环的,即如何证明该数值不可能是快乐数
对于上述问题,我们可以利用哈希表来解决:
- 计算数值的按位平方和,那么首先要取出每一位的数值并计算其平方数,再定义一个平方和变量,累计数值每一位的平方数
- 定义一个哈希表,在过程中,每一次的平方和结果都进行记录
- 当计算出的平方和已经在哈希表中有记录时,则说明该数值在之前的过程中出现过,继续进行过程只会导致无限循环,所以输入的数值不属于快乐数
- 当累计的平方和为1时,则说明我们输入的数值属于快乐数
Java解法(HashSet):
class Solution {
public boolean isHappy(int n) {
Set<Integer> hs = new HashSet();
while(n != 1 && hs.add(n)){
int sum = 0;
while(n != 0){
int l = n % 10;
sum += l * l;
n = n / 10;
}
n = sum;
}
return n == 1;
}
}
1. 两数之和
题目详细:LeetCode.1
两数之和是一道非常基础的算法题,可以很容易的想出解题的方案,例如将数组中的数值依次遍历并与其后的每一个数值都进行相加,判断其两数之和是否与目标值相等,相等则返回两个数值的下标集合。
这显然是最简单的方法,也是最费时间的方法,时间复杂度为O(n2)。
那么有没有时间复杂度更低的方法呢?答案是有的:
- 定义一个哈希表(HashMap),
key:value
分别存储的是数值:下标位置
- 遍历数组,利用哈希表记录每一个数值及其数组下标
- 在每一次循环中,都判断当前数值与目标值的差值,是否存在于哈希表中
- 如果该差值在哈希表中不存在,则说明在数组中暂不存在满足两数之和的数字
- 如果该差值在哈希表中存在,则说明哈希表中记录过数组中的某一个数,且该数可以与当前数值相加得到目标值,通过HashMap提供的get方法获取到其下标位置,最后返回当前数值的下标和哈希表中记录的另一个数值的下标即可
Java解法(HashMap):
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> hs = new HashMap<>();
for(int i=0; i < nums.length; i++){
int val = target - nums[i];
if(hs.containsKey(val)){
return new int[]{i, hs.get(val)};
}
hs.put(nums[i], i);
}
return new int[2];
}
}
今天的题目虽然简单,但也对哈希表的三种不同的存储结构(数组、HashSet、HashMap)进行了一个实际应用,使我对这三个不同的结构的有了更为深刻的理解,需要根据不同的应用场景选用不同的哈希结构,才能最大化的体现出哈希法的优势。
虽然HashSet底层是基于HashMap实现的哈哈,而它们的底层的底层,则是基于数组实现的,不得不说,这数组真是yyds。
所以今日的作结诗是:
不识庐山真面目,只缘身在此山中。