今日任务:
- 哈希表理论基础
- 有效的字母异位词
- 两个数组的交集
- 快乐数
- 两数之和
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
哈希表理论基础:
哈希表中有一个哈希函数(散列函数)。即构建一个确定的映射,它能把关键字映射到一个唯一的存储位置。这种映射应该是我们可以进行计算的。已知关键字,我们应该能算出其地址;反之,已知地址,我们可以检索到对应的关键字。一旦建立起这种关系,那么给定关键字,我就能直接利用这个映射(即所谓的哈希函数)直接算出其地址并寻址。这可大大缩减确定关键字存储位置所花的时间。
哈希碰撞: 当我们对某一个元素进行哈希运算之后得到这个元素的存储位置,之后发现这个位置已经被占有了,这就叫做哈希碰撞,这里有几种方式来处理哈希碰撞,开发地址法,链地址法,再哈希法,我们这里使用的是链地址法,常见的哈希表如,数组,hashset 不可以存储重复的数据,无法通过索引来获取到,hashmap 可以存储重复的数据,可以通过索引获取到,存储的是键值对。
有效的字母异位词
这道题目我没有怎么看,直接看的视频,有一点就是在使用数组作为hash表的时候,通过 s.charAt(i) - 'a' 得到的结果来获取到数组的索引,进行操作,其实就是通过将它们转为 ASCII 码值,执行相减,因为 a-z 都是在这个范围内的,就算得到的是 'a' 那么 - 'a' 结果为 0,索引为 0 这个位置,也是正确的。
class Solution {
public boolean isAnagram(String s, String t) {
int[] arr = new int[26];
//将左边的存入到arr
for (int i = 0; i < s.length(); i++) {
arr[s.charAt(i) - 'a']++;
}
//将右边的从arr取出
for (int j = 0; j < t.length(); j++) {
arr[t.charAt(j) - 'a']--;
}
for (int k = 0; k < 26; k++) {
if (arr[k] != 0) return false;
}
return true;
}
}
无论是,arr[k] 小于 0 获取 arr[k] 大于 0 都代表它们不是字母异位词
两个数组的交集
这个是找到两个数组相交的部分,那么就要考虑相交的部分中不应该有重复的数字,因此我们需要使用 Set 来构建这个哈希表,先将第一个数组中的值存储到对应的Set集合中,之后再遍历第二个数组,如果找到相同的就是用 Set 来存储起来,使用 Set 来存储结果同样也是为了避免结果里面出现重复的元素,比如 Set1 = [1, 2, 3] nums2 = [2, 2, 2] 如果不适用 Set 存储的话,它们的交集就成了 [2, 2, 2] 但是我们应该让它的交集成为 [2] 即可!
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
int[] marker = new int[1024];
Set<Integer> resSet = new HashSet();
for (int i : nums1) {
marker[i] = 1;
}
for (int j : nums2) {
if (marker[j] == 1) {
resSet.add(j);
}
}
return resSet.stream().mapToInt(x -> x).toArray();
}
}
当然刚开始我是创建一个 new int[resSet.size()] 的数组来返回的,不过后面这一串 return 的语句显得高级一点。 这个还有一个数组的版本,不过,在 leetcode 上面运行消耗的时间比上面这个还多1ms,可能是因为创建数组比较消耗时间吧!
两个数组的交集II
这个里面交集里是有重复元素的,这意味着我们不应该使用Set来做这道题,同时由于它的条件
1 <= nums1.length, nums2.length <= 10000 <= nums1[i], nums2[i] <= 1000
所以创建一个数组,也是需要消耗大量的空间的,因此我们就可以使用hashmap来做这道题,首先将第一个数组 nums1 的值存入到 hashmap 当中,key 记录值,value 记录重复的次数,当key存在的时候,获取到对应的value然后将值加一即可! 之后遍历另一个数组,确保另一个hashmap当中存在这个值,同时它的value不小于等于 0,就将这个相交的值存储到一个 list 集合当中,之后将它对应的 value 自减,表示已经记录过了这个重复的元素,最后再使用数组将 list 中的值都获取到,返回即可
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
Map<Integer, Integer> hashMap = new HashMap<>();
List<Integer> res = new ArrayList<>();
//将 nums1 的值存入到 HashMap 当中,key 表示的是 nums1 的值
//value 表示值出现的次数
for (int i : nums1) {
if (hashMap.containsKey(i)) {
Integer o = hashMap.get(i);
hashMap.put(i, ++o);
} else {
hashMap.put(i, 1);
}
}
for (int j : nums2) {
if (hashMap.containsKey(j)) {
Integer integer = hashMap.get(j);
if (integer > 0) {
res.add(j);
hashMap.put(j, --integer);
}
}
}
int[] ret = new int[res.size()];
int index = 0;
for (Integer re : res) {
ret[index++] = re;
}
return ret;
}
}
快乐数
快乐数的关键在于发现,如果它不是快乐数的话,各个位数平方计算的结果就会重复,这个时候我们使用 Set 集合来存储, sum = num while(set.add(sum)) {}如果重复的话就不会进入到 while 循环当中,在外部返回 false,这样就不会导致死循环出不来了 另一个需要注意的点就是我们计算传入的值的平方值,需要首先获取到它们的每一位的值 while(n > 0) { w = n % 10;//取模获取到末尾的元素 res += w * w; n /= 10;//将我们获取到的末尾元素去除,如果说 n 为 1 的话,1 / 10 这时候 n 就会变为 0,然后就会退出这个 while 循环,完整代码如下: }
class Solution {
public boolean isHappy(int n) {
Set<Integer> hashSet = new HashSet<>();
int sum = n;
while (hashSet.add(sum)) {
sum = getDigits(sum);
if (sum == 1) return true;
}
return false;
}
private static int getDigits(int sum) {
int ret = 0;//111
while (sum != 0) {
int tissa = sum % 10;
ret += tissa * tissa;
sum /= 10;
}
return ret;
}
}
两数之和
最简单的思路是暴力双层For循环,但是我们可以利用hashmap的contains方法 n1 + n2 = target target - n1 = n2 当然需要注意,避免包含元素自身
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hashMap = new HashMap<>();
//将所有的元素存入到 hashMap
//key 表示当前元素的值,value 表示当前元素在数组中的索引
for (int i = 0; i < nums.length; i++) {
hashMap.put(nums[i], i);
}
for (int i = 0; i < nums.length; i++) {
int another = target - nums[i];
if (hashMap.containsKey(another) && hashMap.get(another) != i) {
return new int[]{i, hashMap.get(target - nums[i])};
}
}
return new int[]{-1, -1};
}
}
总结:
哈希表(哈希函数,哈希表(数组,set,hashmap),哈希碰撞,链地址法)的基本概念 有效的字母异位数(使用数组来作为哈希表) 两个数组的交集(数组或者是Set结合) 两个数组的交集II(HashMap) 快乐数(找到如果不是快乐数的话计算的值会出现重复,然后我们使用Set集合来存储这里的元素是关键) 两数之和(梦开始的地方)