代码随想录算法训练营第六天 | 242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和

98 阅读6分钟

第三章 - 哈希表 part01

@author LemonCat

@Time 2023/7/31

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

理论基础

建议:大家要了解哈希表的内部实现原理,哈希函数,哈希碰撞

什么时候想到用哈希法,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。 这句话很重要,大家在做哈希表题目都要思考这句话。

文章讲解:代码随想录 (programmercarl.com)

常见的三种哈希结构

  • 数组
  • set - 集合
  • map - 映射

242.有效的字母异位词

建议: 这道题目,大家可以感受到 数组 用来做哈希表 给我们带来的遍历之处。

题目链接:242. 有效的字母异位词 - 力扣(LeetCode)

文章讲解/视频讲解:代码随想录 (programmercarl.com)

方法一 - 哈希表法 - 数组

这个题可以取巧 - 因为字符的范围是小写字母,所以可以用数组当哈希表,minuscule[ch - 'a']​ ​表示字符 ch​ ​的出现次数,这样做更加省时间省内存

  1. 暴力解法:两层 for 循环,同时记录字符是否重复出现

  2. 哈希表法:

    1. 定义一个数组 minuscule 用来上记录字符串 s 里字符出现的次数

    2. 把字符映射到数组也就是哈希表的索引下标上

      1. 映射 minuscule[s.charAt(i) - 'a']​​​
      2. 因为字符 a 到字符 z 的 ASCII 是 26 个连续的数值,所以字符 a 映射为下标 0,相应的字符 z 映射为下标 25
    3. 遍历 字符串 s 将出现元素 +1 -> 这样就将字符串 s 中字符出现的次数,统计出来了。

    4. 遍历 字符串 t 将出现元素 -1

    5. 最后检查 minuscule 数组

      1. ​如果有元素不为零 0​,说明字符串 s 和 t 一定是谁多了字符或者谁少了字符 -> 不符合异位词 return false​​
      2. 若全为零 则是异位词 return true​​
/**
 * 方法一 - 哈希表法
 */
class Solution {
    public boolean isAnagram(String s, String t) {

        // 小写字母哈希表
        int[] minuscule = new int[26];

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

        // 遍历 t 字符串 将对的字符出现的个数 记录数组中(-)
        for (int j = 0; j < t.length(); j++) {
            minuscule[t.charAt(j) - 'a']--;
        }
        for (int k : minuscule) {
            if (k != 0) {
                return false;// minuscule 数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
            }
        }

        return true; // minuscule 数组所有元素都为零0,说明字符串s和t是字母异位词
    }
}
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

349. 两个数组的交集

建议:本题就开始考虑 什么时候用 set 什么时候用数组,本题其实是使用 set 的好题

但是后来力扣改了题目描述和 测试用例,添加了 0 <= nums1[i], nums2[i] <= 1000 条件,所以使用数组也可以了,不过建议大家忽略这个条件。 尝试去使用 set。

题目链接:349. 两个数组的交集 - 力扣(LeetCode)

文章讲解/视频讲解:代码随想录 (programmercarl.com)

思路

  • 输出结果中的每个元素一定是唯一的 -> 结果是去重的, 同时可以不考虑输出结果的顺序 -> 用 set 集合会更好一点
  • 题目描述中数值范围比较小 -> 用数组解决更好一点
  1. 双 set

    1. 优点:自动帮助我们去重;在数据量较大时 更节省空间
    2. 缺点:直接使用 set 不仅占用空间比数组大,而且速度要比数组慢
  2. 双数组

    1. 优点:由于题中数值范围较小 -> 用数组速度会更快
    2. 缺点:面对数据量较大 或 数据跨度较大(稀疏矩阵)-> 占用空间较大

方法一 - 双 set

  1. 用一个哈希集合存放一个数组中的元素,会自动去重。
  2. 遍历另一个数组,当访问到的元素出现在哈希集合中时 -> 将其放到结果集中 resSet
  3. 将 resSet 集合 -> arr 数组
/**
 * 方法一 - 两个 set + 一个数组
 */
class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {

        // 若两个数组传入的有一个是 null -> 提前返回
        if (nums1 == null || nums2 == null) {
            return null;
        }

        Set<Integer> hashSet = new HashSet<>(); // Hash 表
        Set<Integer> resSet = new HashSet<>(); // 结果集

        // 将nums中的值 存储到 hashSet 中
        for (int n1 : nums1) {
            hashSet.add(n1);
        }

        // 将nums2中的值 与 hashSet 中的值作比较 若存在则放到结果集 resSet 中(可以帮我们自动去重)
        for (int n2 : nums2) {
            if (hashSet.contains(n2)) {
                resSet.add(n2);
            }
        }

    
        // 将 set 转换成数组
        int[] arr = new int[resSet.size()];

        int j = 0;
        for (Object o : resSet) {
            arr[j++] = (int) o;
        }

        return arr;
    }
}

改进

  • 将 set 转换成数组
  • 原来的写法
int[] arr = new int[resSet.size()];

int j = 0;
for (Object o : resSet) {
    arr[j++] = (int) o;
}
  • 更优雅的写法

    • 具体详情以后有时间在研究
int[] arr = resSet.stream().mapToInt(x -> x).toArray();

方法二 - 双数组

/**
 * 方法二 - 双数组
 */

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {

        // 若两个数组传入的有一个是 null -> 提前返回
        if (nums1 == null || nums2 == null) {
            return null;
        }

        int[] hashArr = new int[1001]; // Hash 表
        int[] resArr; // 结束数组

        // 遍历 num1 将其放到对应的hash表中
        for (int i : nums1) {
            hashArr[i]++;
        }

        // 遍历 num2 若发现便利的对象在hash表中已经存在了 证明是交集 -> 存入结果集中
        Set<Integer> resSet = new HashSet<>();
        for (int j : nums2) {
            if (hashArr[j] != 0) {
                resSet.add(j);
            }
        }
        resArr = new int[resSet.size()];

        // 将结果集 -> 转换成 数组
        int j = 0;
        for (Integer res : resSet) {
            resArr[j++] = (int) res;
        }
        return resArr;
    }
}

202. 快乐数

建议:这道题目也是 set 的应用,其实和上一题差不多,就是套在快乐数一个壳子

题目链接:202. 快乐数 - 力扣(LeetCode)

文章讲解:代码随想录 (programmercarl.com)

方法 - set 集合

  • 有限大小的整数都可以在有限次数的替换运算中压缩到有限位数,所以如果不是快乐数一定会出现循环
  • 那么对一个数不断做迭代替换过程中如果出现了不在 1 处的循环则一定不是快乐数
  • 所以用 Set​​ 存放过程中所有的状态,每次替换就在这个 Set​​ 中查询是否到达过,如果到达过则返回 false​​
/**
 * 方法 - set 集合
 */
class Solution {
    public boolean isHappy(int n) {
        Set<Integer> hashSet = new HashSet<>(); // 记录每次平方和结果集
        while (true) {

            int res = 0; // 每次平方和
            int[] arrN = getNum(n); // 传入数 返回数每一位数组成的数组

            // 计算每个数平方后的和
            for (int i : arrN) {
                res += i * i;
            }

            // 如果结果是 1 就是快乐数
            if (res == 1) {
                return true;
            }

            // 结果集中存在 结束 -> 发生重复 -> 不是快乐数
            if (hashSet.contains(res)) {
                return false;
            }

            hashSet.add(res); 
            n = res;

        }
    }

    /**
     * 传入数 返回数每一位数组成的数组
     *
     * @param n
     * @return
     */
    int[] getNum(int n) {

        int length = ((Integer) n).toString().length(); // 获取位数的长度

        int[] arrN = new int[length]; // 返回的数组

        for (int i = 0; i < length; i++) {
            arrN[i] = n % 10;
            n /= 10;
        }
        return arrN;
    }
}

1. 两数之和

建议:本题虽然是 力扣第一题,但是还是挺难的,也是 代码随想录中 数组,set 之后,使用 map 解决哈希问题的第一题。

建议大家先看视频讲解,然后尝试自己写代码,在看文章讲解,加深印象。

题目链接 1. 两数之和 - 力扣(LeetCode)

文章讲解/视频讲解:代码随想录 (programmercarl.com)

思路:

  1. 访问到一个数 nums[i]​,与之和为 target​ 的数只能是 target-nums[i]​

  2. 将每次便利的数存入 Map 中

    • 注意:K-V 分别对应的值!!!

      • K:数值
      • V:索引
    • 因为我们最终是要找对应数值的索引 -> arr[1] = map.get(target - nums[i])​

  3. 然后找 target-nums[i]​ 是否在 Map 中出现过 -> 将当前索引 和 将找到的 数值对应的 V 存入数组

  4. 返回数组~

方法一 - Map 方法

class Solution {
    public int[] twoSum(int[] nums, int target) {

        HashMap<Integer, Integer> map = new HashMap<>();
        int[] arr = new int[2];

        if (nums == null || nums.length == 0) {
            return arr;
        }

        // K - V
        // K:数值
        // V:索引
        for (int i = 0; i < nums.length; i++) {

            // 遍历当前元素,并在map中寻找是否有匹配的key
            if (map.containsKey(target - nums[i])) {
                arr[0] = i;
                arr[1] = map.get(target - nums[i]);
                return arr;
            }

            // 如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.put(nums[i], i);
        }
        return arr;
    }
}
  • 明天加油!!!