05| ● 哈希表理论基础 ● 242.有效的字母异位词 ● 349. 两个数组的交集 ● 202. 快乐数 ● 1. 两数之和

129 阅读2分钟

哈希表理论基础

关于哈希表的一些基础概念,如哈希函数和哈希冲突,可以参考博客:你是怎么看懂HashMap的

关于哈希表的具体实现方式,可以参考:深入理解哈希表

有效的字母异位词

题目链接

哈希表的实现方式有三种,分别是数组、Set和Map,数组适用于数据量不大的情况,在数据量很大时,需要考虑使用Set,如果要用到数据的两个属性,可以考虑Map中的key_value形式。

这道题使用数组来实现哈希表,创建哈希表来存储已经遍历过的元素。

如果使用暴力解法,使用两层for循环,那么时间复杂度是 O(n^2)。

注意代码中for each的写法,for(int count : record)这里的循环判断条件是 "enhanced for loop",也称为 "for-each loop"。这是 Java 5 引入的一个新特性,主要用于遍历数组或集合。record 是一个整数数组。这个循环将遍历 record 数组中的每一个元素,将元素值赋给 count,然后执行循环体中的语句。

/**
 * 242. 有效的字母异位词 字典解法
 * 时间复杂度O(m+n) 空间复杂度O(1)
 */
class Solution {
    public boolean isAnagram(String s, String t) {
        int[] record = new int[26];

        for (int i = 0; i < s.length(); i++) {
            record[s.charAt(i) - 'a']++;     // 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
        }

        for (int i = 0; i < t.length(); i++) {
            record[t.charAt(i) - 'a']--;
        }
        
        for (int count: record) {
            if (count != 0) {               // record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
                return false;
            }
        }
        return true;                        // record数组所有元素都为零0,说明字符串s和t是字母异位词
    }
}

两个数组的交集

题目链接 求两个数组的交集,并且要求元素是唯一的,可以使用Set构建哈希表,Set有自动去重的功能,先遍历数组1并存入哈希表,在遍历数组2,判断是否有映射在哈希表的元素,最后创建res数组存储交集元素。

  • 在Java中,数组的大小是固定的,一旦创建就不能改变。因此,你不能直接向Java数组中添加元素。想要对数组进行扩容,只能①创建一个新的更大的数组;②使用ArrayList;

  • 这里的length没加括号()是因为,数组取的是属性,不用括号,而字符串取的是方法,要加括号

  • 注意多态的使用,父类引用指向子类对象,HashSet是Set的子类 Set <Integer> set1 = new HashSet<>();

  • 在Java中,Set和HashSet都是集合类型,但它们在使用和实现上有一些区别。 Set 是Java集合框架中的一个接口,它继承自Collection接口。Set接口的特点是不允许集合中有重复的元素,即任何两个元素e1和e2都不能满足e1.equals(e2)。Set接口没有提供任何额外的方法,它只使用了Collection接口的方法。Set接口有多个实现类,包括HashSetLinkedHashSetTreeSet等。 HashSet 是Set接口的一个实现类。它使用哈希表(实际上是一个HashMap实例)来存储元素。HashSet允许存储null元素。HashSet的特点是最能够提供常数时间复杂度的基本操作(addremovecontains),因此在性能上通常优于其他Set实现类。 当你写Set<Integer> resSet = new HashSet<>();时,你实际上是在创建一个HashSet实例,并将其引用赋值给一个Set类型的变量。这是一种常见的编程实践,称为“程序面向接口编程”。这样做的好处是,你可以在不改变代码其他部分的情况下,改变resSet的实际实现类型。例如,你可以将其改为Set<Integer> resSet = new TreeSet<>();,而不需要修改使用resSet的代码。 这种做法提高了代码的灵活性,使得代码更容易适应需求的变化。同时,这也是一种隐藏实现细节的方式,使得代码更容易理解和维护。

import java.util.HashSet;
import java.util.Set;

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
            return new int[0];
        }
        Set<Integer> set1 = new HashSet<>();
        Set<Integer> resSet = new HashSet<>();
        //遍历数组1
        for (int i : nums1) {
            set1.add(i);
        }
        //遍历数组2的过程中判断哈希表中是否存在该元素
        for (int i : nums2) {
            if (set1.contains(i)) {
                resSet.add(i);
            }
        }
      
        //方法1:将结果集合转为数组

        return resSet.stream().mapToInt(x -> x).toArray();
        
        //方法2:另外申请一个数组存放setRes中的元素,最后返回数组
        int[] arr = new int[resSet.size()];
        int j = 0;
        for(int i : resSet){
            arr[j++] = i;
        }
        
        return arr;
    }
}

快乐数

题目链接

题中的无限循环是解题的关键点所在,这是我们跳出循环的判断条件。这里n的数值取值范围很大,所以考虑使用Set构建哈希表,利用哈希表存储已经求得过的结果,在下一次求出结果时遍历,若得到已经存在哈希表中的数值,则是开始无限循环,需要填=跳出循环体。

class Solution {
    public boolean isHappy(int n) {
        //n取值很大,使用set
        //创建哈希表记录求过的结果,以跳出无限循环
        Set<Integer> record = new HashSet<>();
        while(n != 1 && !record.contains(n)){
            record.add(n);
            n = getNextNumber(n);
        }
        //返回是否为快乐数
        return n == 1;

    }
    //平方求和函数
    private int getNextNumber(int n){
        int res = 0;
        while(n != 0){
            int tmp = n % 10;
            res += tmp * tmp;
            n /= 10;
        }
        return res;

    }
}

两数之和

题目链接

这道题我们既要获取所需元素的大小,又要返回所需元素的下标,所以考虑使用Map的key_value来存储,key来存储元素数值,value存储元素下标,一定要搞清楚key和value分别代表什么

创建Map哈希表:Map<Integer, Integer> map = new HashMap<>();

向Map插入数据:map.put(nums[i], i);

class Solution {
    public int[] twoSum(int[] nums, int target) {
        /*这道题有四个需要注意的地方
        1:为什么用哈希表:这里设置的数据量很大,且要判断元素是否出现在遍历过的元素里面,
        所以先考虑哈希映射;
        2:为什么使用map:在寻找题解的时候,我们不仅要知道元素的数值,还要知道元素的下标,
        所以使用map的key-value的形式存储;
        3:map的作用是什么:map在这里是用来存放已经遍历过的元素,以判断是否满足两数之和为target;
        4:key和value分别代表的是什么:这里我们要判断元素的加和是否为target,所以把元素设置为key,
        value则是元素所在的小标;
        */
        //创建一个数组来存储符合条件的两个元素的下标
        //创建map哈希表来存储遍历过的元素
        int[] res = new int[2];
        if(nums == null || nums.length == 0)
            return res;
        Map<Integer, Integer> map = new HashMap<>();
        //遍历数组,并把不确定的元素存入哈希表map中
        for(int i = 0; i < nums.length; i++){
            int tmp = target - nums[i];
            if(map.containsKey(tmp)){
                //res存储符合的两个元素的下标
                res[0] = map.get(tmp);
                res[1] = i;
                //找到答案后要跳出循环
                break;
            }
            map.put(nums[i], i);
        }
        //返回res
        return res;

    }
}