day 6 第三章 哈希表

1,173 阅读4分钟

今日任务:

  • 哈希表理论基础
  • 有效的字母异位词
  • 两个数组的交集
  • 快乐数
  • 两数之和

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。

哈希表理论基础:

哈希表中有一个哈希函数(散列函数)。即构建一个确定的映射,它能把关键字映射到一个唯一的存储位置。这种映射应该是我们可以进行计算的。已知关键字,我们应该能算出其地址;反之,已知地址,我们可以检索到对应的关键字。一旦建立起这种关系,那么给定关键字,我就能直接利用这个映射(即所谓的哈希函数)直接算出其地址并寻址。这可大大缩减确定关键字存储位置所花的时间。

哈希碰撞: 当我们对某一个元素进行哈希运算之后得到这个元素的存储位置,之后发现这个位置已经被占有了,这就叫做哈希碰撞,这里有几种方式来处理哈希碰撞,开发地址法,链地址法,再哈希法,我们这里使用的是链地址法,常见的哈希表如,数组,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 <= 1000
  • 0 <= 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集合来存储这里的元素是关键) 两数之和(梦开始的地方)