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

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

哈希表理论基础

代码随想录

哈希表

image-20221121095432622

哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素

解决的问题:一般哈希表都是用来快速判断一个元素是否出现集合里

查询时间复杂度:O(1)

哈希函数

通过哈希函数,可以把其它数据格式的数据转化为不同的数值

image-20221121095805984

哈希碰撞

如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞

哈希表3

哈希碰撞解决办法

拉链法

刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了

其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间

线性探测法

前提:保证tableSize大于dataSize,需要依靠哈希表中的空位来解决碰撞问题

例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。

常见的三种哈希结构

  • 数组
    • 数据较少,且范围可控时使用
  • set (集合)
    • 数据较多,且不知道范围
  • map(映射)
    • 有key-value的情况使用

242. 有效的字母异位词

代码随想录

视频

遇到的问题

  1. 字符对象没法用hashCode函数
  2. 哈希冲突如何解决(比如只有一个输入“a”,”b”怎么解决) ->不应该用字符串长度定义计数数组,而应该用26

本人初次代码

class Solution {
    public boolean isAnagram(String s, String t) {
        int lengthS = s.length();
        int lengthT = t.length();
        if(lengthS != lengthT){
            return false;
        }
        if(s == t){
            return true;
        }
        String[] strS = s.split("");
        String[] strT = t.split("");
        System.out.println(Arrays.toString(strS));
        System.out.println(Arrays.toString(strT));
        int[] countArrayS = new int[26];
        int[] countArrayT = new int[26];
        for(int i = 0;i < lengthS;i++){
            int hashCodeS = strS[i].hashCode() % 26;
            countArrayS[hashCodeS]++;
        }
        for(int j = 0;j < lengthT;j++){
            int hashCodeT = strT[j].hashCode() % 26;
            countArrayT[hashCodeT]++;
        }
        for(int index = 0;index < 26;index++){
            if(countArrayS[index] != countArrayT[index]){
                return false;
            }
        }
        return true;
    }
}

思路

  1. 数组其实就是一个简单哈希表,而且这道题目中字符串只有小写字符,那么就可以定义一个数组,来记录字符串s里字符出现的次数

    • 定一个数组叫做record,记录字符串s里字符出现的次数,大小为26 就可以了,初始化为0,因为字符a到字符z的ASCII也是26个连续的数值

    • 需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25

  2. 遍历字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了

  3. 那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作

  4. 那么最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false

时间复杂度为O(n),空间上因为定义是的一个常量大小的辅助数组,所以空间复杂度为O(1)

代码随想录代码

/**
 * 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']++;
        }

        for (int i = 0; i < t.length(); i++) {
            record[t.charAt(i) - 'a']--;
        }
        
        for (int count: record) {
            if (count != 0) {
                return false;
            }
        }
        return true;
    }
}

可进步的地方

  1. 其实只用设置一个计数数组,遍历s字符串的时候加,遍历t字符串的时候减,最后看数组各个元素是否都等于0即可判断
  2. 不需要用hash函数,用ascii码即可

349. 两个数组的交集

代码随想录

视频

本人代码

class Solution {
        public int[] intersection(int[] nums1, int[] nums2) {
        HashSet<Integer> setNums1 = new HashSet<>();
        HashSet<Integer> setSame = new HashSet<>();
        for (int i = 0; i < nums1.length; i++) {
            if (setNums1.contains(nums1[i])){
                continue;
            }
            setNums1.add(nums1[i]);
        }
        for (int i = 0; i < nums2.length; i++) {
            if (!setNums1.contains(nums2[i])){
                continue;
            }
            setSame.add(nums2[i]);
        }
        int[] result = new int[setSame.size()];
        int index = 0;
        for (Integer sameEle : setSame) {
            result[index++] = sameEle;
        }
        return result;
    }
}

自己ac嘿嘿

思路

代码随想录代码

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);
            }
        }
        //将结果几何转为数组
        return resSet.stream().mapToInt(x -> x).toArray();
    }
}

可进步的地方

  1. set的add方法可以自然去重,无需先进行判断是否重复再加入
  2. 可以多用用Stream流,这样可以方便把Collection对象,转化为想要的对象,比如数组

202. 快乐数

代码随想录

思路

  1. 像此类数学题,可以先自己拿例子尝试,去看看怎样才不是快乐数,就会发现求和的过程中,sum会重复出现此时就不是快乐数
  2. 遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了

本人代码

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

代码随想录代码

class Solution {
    public boolean isHappy(int n) {
        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 temp = n % 10;
            res += temp * temp;
            n = n / 10;
        }
        return res;
    }
}

1. 两数之和

代码随想录

视频

思路

  1. 因为本地,我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适

  2. map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下表,这样才能找到与当前元素相匹配的(也就是相加等于target)

    那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标

    所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下表}

  3. 在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素

个人问题

  1. 用数组下标当作key,把数组的元素值作为value,导致遇到 2 / target 的元素混入各种正确元素位置中,不知如何解决

个人代码

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < nums.length;i++){
            if(map.containsKey(target - nums[i])){
                return new int[]{map.get(target - nums[i]),i};
            }
            map.put(nums[i],i);
        }
        return null;
    }
}

总结

为什么会想到用哈希表

​ 当需要查询另一个集合里的元素时我们会想到用到哈希表

三种数据结构如何使用

  • 数组
    • 数据较少,且范围可控时使用
  • set (集合)
    • 数据较多,且不知道范围
  • map(映射)
    • 有key-value的情况使用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值