03 哈希表-常见题型及思路总结

哈希表基础知识

哈希表的概念

哈希表(hash table)也被称为散列表,可以根据关键字快速返回相应的值,遍历时间复杂度仅仅为O(1),十分神奇。

哈希函数:其原理主要是通过哈希函数对输入值进行均匀散射处理,得到唯一的输出值,那么输入值被称为键,而该得到的输出值就被称为值。展开来说,哈希函数对输入的值例如“小明”进行映射,将其转为固定长度数组的下标,那么通过该下标我们就可以快速判断“小明”是否存在。

哈希碰撞:当输入值过多,数组长度不够,不同的输入值映射到同一个地方怎么办?有两种解决方法。一是拉链法,通过在数组元素后面添加链表进行储存。二是线性探测法,在哈希表容量大于输入元素数量情况下,当映射的空位有存放时,将其存放在下一个空位。

常见的哈希表类型:

1. 数组:数组也是一种哈希表,可以通过其下标快速查找元素。下标相当于key,里边的元素相当于value。

2. set:是一种只储存值的哈希表,类似于集合。

3. map:是一种可以储存键值对的哈希表,通过键可以快速访问值,是一种映射。

哈希表的语法

HashSet

import java.util.HashSet;

public class HashSetExample {
    public static void main(String[] args) {
        HashSet set = new HashSet<>();
        
        // 添加元素
        set.add("apple");
        set.add("banana");
        set.add("orange");
        set.add("apple"); // 重复元素
        
        // 删除元素
        set.remove("banana");
        
        // 遍历元素
        for (String fruit : set) {
            System.out.println(fruit);
        }
        
        // 判断元素是否存在
        boolean containsApple = set.contains("apple");
        System.out.println("Contains apple: " + containsApple);
    }
}

HashMap

import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        HashMap map = new HashMap<>();
        
        // 添加键值对
        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");
        map.put(1, "uno"); // 覆盖已存在的键
        
        // 删除键值对
        map.remove(2);
        
        // 获取值
        String value = map.get(3);
        System.out.println("Value for key 3: " + value);
        
        // 遍历键值对
        for (Integer key : map.keySet()) {
            String val = map.get(key);
            System.out.println(key + " -> " + val);
        }
        
        // 判断键是否存在
        boolean containsKey = map.containsKey(1);
        System.out.println("Contains key 1: " + containsKey);
    }
}

哈希表的使用思路

使用hashset:

1. 元素需要去重,因为hashset只能储存不一样的元素,会自动去除重复的元素。

2. 关心元素是否出现,因为hashset的查找元素很方便,只需要O(1)的时间。

使用hashmap:

1. 储存一对数据时,用hashmap比较方便。

2. 需要进行一对数据的快速查找。

242. 有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

  • s 和 t 仅包含小写字母

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

思路:

很容易可以想到字符本身和该字符出现的次数是一个键值对,字符——键,字符出现的次数——值,本题应该用hashmap来解。

那么如何表示字符呢?char本质是数字,而字符仅包含小写字母,因此可以用数字表示。键和值都是数字,自然考虑数组。

用 字符-'a' 表示字符的键,用数组的下标表示,出现的次数用对应数组元素表示。

class Solution {
    public boolean isAnagram(String s, String t) {
        int [] load = new int [26];
        for(int i = 0; i < s.length(); i++){
            load[s.charAt(i) - 'a']++;
        }
        for(int i = 0; i < t.length(); i++){
            load[t.charAt(i) - 'a']--;
        }
        for(int num : load){
            if(num != 0){
                return false;
            }
        }
        return true;

    }
}

349. 两个数组的交集

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

思路:

交集其实就是该元素有没有存在在两个数组中。

可以将其中一个数组转为hashset,然后用第二个数组遍历进行contain比较,将true的结果返回到一个新数组中。

由于其大小不知道可以用动态数组储存,并使用增强for循环遍历取出。

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 set1 = new HashSet<>();
        Set 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;
    }
}

202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

思路:

这题就是模拟平方和的过程,但其可能会无限循环,那么如何判断新生成的数有没有出现过呢?

自然想到得hashset用保存其出现过的数,看看其会不会无限循环。

class Solution {
    public boolean isHappy(int n) {
        int number = n;
        int sum = 0;
        Set record = new HashSet<>();
        while(n != 1){
            while(n >= 10){
                sum += (n % 10) * (n % 10);
                n = n/10;
            }
            sum += n*n;
            n = sum;
            sum = 0;
            if(record.contains(n)){
                return false;
            }
            record.add(n);
        }
        return true;

    }
}

001. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

思路:

该题可以两层for暴力破解。

由于查询的是数组的下标,因此可以考虑将数组的key-value反转。反转之后不仅可以去重,并且可以快速判断是否存在某个数。

用for遍历第二个数组,target - num[i] == 某个数,那么该数就是确定的,可以被快速判断出来。

public int[] twoSum(int[] nums, int target) {
    int[] res = new int[2];
    if(nums == null || nums.length == 0){
        return res;
    }
    Map map = new HashMap<>();
    for(int i = 0; i < nums.length; i++){
        int temp = target - nums[i];   // 遍历当前元素,并在map中寻找是否有匹配的key
        if(map.containsKey(temp)){
            res[1] = i;
            res[0] = map.get(temp);
            break;
        }
        map.put(nums[i], i);    // 如果没找到匹配对,就把访问过的元素和下标加入到map中
    }
    return res;
}

你可能感兴趣的:(散列表,算法,数据结构)