(待整理)代码随想录算法训练营D6|哈希表理论基础,242. 有效的字母异位词,349. 两个数组的交集,202. 快乐数,1. 两数之和

目录

哈希表理论基础

哈希表

内部实现原理

哈希函数

哈希碰撞

解决方法

常见哈希结构

数组

set 和map

242. 有效的字母异位词

349. 两个数组的交集

202. 快乐数

1. 两数之和


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

哈希表理论基础

哈希表

Hash table,也叫散列表。是一种通过键来访问值的数据结构),数组也是一种哈希表(通过下标访问数组中的元素)。可以用来快速判断一个元素是否在集合里(时间复杂度O(1))。

内部实现原理

哈希函数

将其他数据格式hashcode转换映射为哈希表上的索引数字的函数,若hashcode得到的数值大于哈希表的tablesize,会再次对数值进行一个取模操作,使得映射出来的索引数值都落在哈希表上。

哈希碰撞

当有两个或多个数值的hashcode映射到哈希表同一索引下标的位置时,我们称之为哈希碰撞。

解决方法

  • 拉链法
    • 发生冲突的元素被存储在链表中,通过索引找到冲突元素
    • 要选择适当的哈希表大小,避免因为数组空值而浪费大量内存,也避免链表太长导致查找费时
  • 线性探测法
    • 发生冲突的元素一个放在冲突的位置,其余的元素向下找一个空位存放
    • tablesize一定要大于datasize(哈希冲突的数据个数)

常见哈希结构

数组,set(集合),map(映射)。

数组

用数组下标来做映射

set 和map

set是一个集合,里面放的元素只能是一个key

哈希法是空间换时间

哈希值比较小,且数值范围可控时,选用数组

如果哈希值比较少、特别分散、跨度非常大,用数组就造成空间的极大浪费,使用set会比较合适

242. 有效的字母异位词

经典的数组在哈希表的应用

将字符串中出现的所有字母频次统计在哈希数组中,再对哈希数组根据另一字符串字母出现频次做减减操作,最终数组出现非零元素表明两字符串非有效的字母异位词

public class ValidAnagram {

    /**
     * 数组在哈希表的应用
     * 利用26位字母在ascii码中是连续的特性,将字符串映射到数组上来统计字母出现的次数
     * 再利用减减方式,判断最终数组的所有元素是否为0,出现非0元素,则表明s和t不是字母异位词
     *
     * @param s
     * @param t
     * @return
     */
    public boolean isAnagram(String s, String t) {
        // 定义一个record数组来统计字符串s中字符出现的次数
        int[] record = new int[26];

        // 因为小写a到小写z的ASCII码是26个连续的数值,所以字符a映射到数组下标为0的位置,其他依次推出
        for (char sChar : s.toCharArray()) {
            record[sChar - 'a']++;
        }

        // 字符串t出现的字符次数做减减操作
        for (char tChar : t.toCharArray()) {
            record[tChar - 'a']--;
        }

        // 如果数组最终存在元素为非0,说明有字符串少了/多了字符
        for (int count : record) {
            if (count != 0) {
                return false;
            }
        }
        return true;
    }
}

 

349. 两个数组的交集

使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。

public class IntersectionOfTwoArrays {

    /**
     * 使用set这种哈希结构查找元素
     *
     * @param nums1
     * @param nums2
     * @return 交集
     */
    public int[] hashsetMethod(int[] nums1, int[] nums2) {
        // 判断数组是否为空
        if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
            return new int[0];
        }
        // 返回的交集是去重后的结果
        HashSet<Integer> resultSet = new HashSet<>();
        HashSet<Integer> numSet = new HashSet<>();

        // 将num1数组中出现的字母在哈希set中做记录
        for (int i : nums1) {
            numSet.add(i);
        }

        // 在set中查找num2的元素,并添加到结果集中
        for (int j : nums2) {
            if (numSet.contains(j)) {
                resultSet.add(j);
            }
        }

        // 返回int数组
        int[] resultArr = new int[resultSet.size()];
        int n = 0;
        for (Integer r : resultSet) {
            resultArr[n++] = r;
        }
        return resultArr;
    }


    /**
     * 使用哈希数组的方式查找元素(仅适用于数值范围可控/较小),查询会比set快,并且占用空间少
     * 题目后修改了数值范围:
     * 1 <= nums1.length, nums2.length <= 1000
     * 0 <= nums1[i], nums2[i] <= 1000
     *
     * @param nums1
     * @param nums2
     * @return
     */
    public int[] hashArrayMethod(int[] nums1, int[] nums2) {
        // 判断数组是否非空
        if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
            return new int[0];
        }
        // 结果集
        HashSet<Integer> resultSet = new HashSet<>();
        // 定义记录nums1的数组,大小比最大值大一点即可
        int[] hash = new int[1002];

        // 出现的字母在哈希数组做记录,其对应元素值为1,表示有该数值
        for (int nums : nums1) {
            hash[nums]++;
        }

        // 查找num2的元素,并添加到结果集
        for (int nums : nums2) {
            if (hash[nums] != 0) {
                resultSet.add(nums);
            }
        }

        int[] resultArr = resultSet.stream().mapToInt(x -> x).toArray();
        return resultArr;
    }
}

202. 快乐数

此题有两个关键:

  • 快速判断一个元素是否出现在集合里,选用哈希法
  • 对取数值各个位上的单数操作

 

public class HappyNumber {

    /**
     * 判断一个元素是否已出现在集合里 -> 哈希法
     * @param n
     * @return
     */
    public boolean isHappy(int n) {
        HashSet<Integer> record = new HashSet<>();

        // 不为1且过程中还未出现该数值
        while (n != 1 && !record.contains(n)) {
            record.add(n);
            n = getNextNumber(n);
        }

        // 为1,则是快乐数;重复出现数值了,则不是快乐数
        return n == 1;
    }

    /**
     * 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和
     * @param n
     * @return
     */
    public int getNextNumber(int n) {
        int result = 0;
        while (n > 0) {
            int tmp = n % 10;
            result += tmp * tmp;
            n /= 10;
        }
        return result;
    }
}

 

1. 两数之和

此题关键:

  • 需要一个集合去记录已遍历过的元素
  • 由于需要查找已遍历过的元素,我们选择用哈希表
  • 我们既需要存储元素,也需要记录元素对应的下标,所以选择map这种数据结构
  • map的key来存遍历过的元素,方便快速查找,map的value则存元素在数组中的下标
public class TwoSum {

    /**
     * 哈希map记录遍历过的元素(key)和元素对应下标(value)
     * @param nums
     * @param target
     * @return
     */
    public int[] twoSum(int[] nums, int target) {
        // 判断入参是否非空
        if (nums == null || nums.length == 0) {
            return null;
        }

        // 定义一个记录遍历过元素的数据结构
        Map<Integer, Integer> recordMap = new HashMap<>();
        int tmp;
        for (int i = 0; i < nums.length; i++) {
            tmp = target - nums[i];
            // 查询是否已经遍历过该元素的余数
            if (recordMap.containsKey(tmp)) {
                return new int[]{i, recordMap.get(tmp)};
            } else {
                // 没有,则将元素添加至map继续遍历
                recordMap.put(nums[i], i);
            }
        }
        return null;
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值