代码随想录day06:242.有效的字母异位词、349. 两个数组的交集、第202题. 快乐数、1. 两数之和

文章介绍了哈希表的基本概念,包括数组、set和map的哈希结构,并通过实例展示了如何在有效字母异位词、两个数组的交集、快乐数和两数之和等题目中运用哈希法。着重讨论了哈希法的空间和时间复杂度,以及与set和数组的适用场景对比。
摘要由CSDN通过智能技术生成

哈希表

哈希表是根据关键码的值而直接进行访问的数据结构。
常见的三种哈希结构:
数组
set(集合)
map(映射)
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

242.有效的字母异位词

题目链接
代码随想录
思路:字母异位词,即两个单词中都有相同的字母以及个数。通过字母的ASCII码来统计,比如’a’就是下标0,'z’就是下标25。为什么不能通过直接相加ASCII码看是否相同来判定,因为bbb和abc这种用例。
数组就是简单的hash表,但是使用数组的前提是有限无重复

class Solution {
    public boolean isAnagram(String s, String t) {
        if(s.length()!=t.length())return false;
        //26个字母的数组hash表
        int [] record=new int[26];
        //统计s中每个字母以及个数
        for(int i=0;i<s.length();i++){
            record[s.charAt(i)-'a']++;
        }
        //统计t中每个字母以及个数
        for(int j=0;j<t.length();j++){
            record[t.charAt(j)-'a']--;
        }
        //假如s中每个字母都都与t中每个字母数量相同,则hash表应该都为0
        for(int k=0;k<26;k++){
            if(record[k]!=0)return false;
        }
        return true;

    }
}

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

#349. 两个数组的交集

题目链接
代码随想录
思路:通过两个hash表记录nums1和nums2中出现过的数字,两个hash表中相同的元素就为交集的元素。因为这道题给出了n的大小,所以也可以使用数组hash。

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        int[] hash1=new int [1001];
        int[] hash2=new int [1001];
        for(int i=0;i<nums1.length;i++){
            hash1[nums1[i]]++;
        }
        for(int j=0;j<nums2.length;j++){
            hash2[nums2[j]]++;
        }
        List<Integer> resList = new ArrayList<>();
        for(int i = 0; i < 1001; i++)
            if(hash1[i] > 0 && hash2[i] > 0)
                resList.add(i);
        //申请一个数组存放resList中的元素,最后返回数组
        int anr[] = new int[resList.size()];
        for(int i=0;i<resList.size();i++){
            anr[i] = resList.get(i);
        }
        return anr;
    }
}

时间复杂度: O(n + m) ;m 是最后要把 set转成vector
空间复杂度: O(n)
假如这里数组的大小没限制在1000以内,就不能用hash数组了。

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        if (nums1 == null  || nums2 == null ) {
            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);
            }
        }
        //申请一个数组存放setRes中的元素,最后返回数组
        int[] arr = new int[resSet.size()];
        int j = 0;
        for(int i : resSet){
            arr[j++] = i;
        }
        return arr;
    }
}

时间复杂度: O(n + m) ;
空间复杂度: O(n)
如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费
那有同学可能问了,遇到哈希问题我直接都用set不就得了,用什么数组啊。
直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。不要小瞧这个耗时,在数据量大的情况,差距是很明显的。
所以当hash值多的时候且紧密的时候用数组,比如统计字母;当hash值少,跨度大的时候用set。

第202题. 快乐数

题目链接
代码随想录
思路:一个是求和,一个是判断和是否出现过(是否开始循环);
求和主要是取数值各个位上的单数:
’%‘运算符:取模运算符,n%10即n个位上的数;
’/‘运算:int/int自动取整,所以n/10即去掉个位后的数;

    int getSum(int n) {
        int sum = 0;
        while (n) {
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }

判断sum是否重复出现,可以用set.contains(sum)来检验sum是否出现过。
正如:关于哈希表,你该了解这些!中所说,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。

class Solution {
    public int getSum(int n){
        int sum=0;
        while(n>0){
            sum+=(n%10)*(n%10);
            n/=10;
        }
        return sum;
    }
    public boolean isHappy(int n) {
        Set<Integer>set =new HashSet<>();
        set.add(n);
        while(n!=1){
            n=getSum(n);
            //假如sum之前出现过
            if(set.contains(n))return false;
            set.add(n);
        }
        return true;
    }
}

1. 两数之和

题目链接
代码随想录
思路:先排序,再用双指针,但是最后需要返回原数组下标,所以排序前需要copy原数组。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        //copy原数组
        int[] newNums = (int[])Arrays.copyOf(nums,nums.length);
        //排序
        Arrays.sort(nums);
        int i=0;
        int j=nums.length-1;
        while(i<nums.length){
            if(nums[i]+nums[j]<target)i++;
            else if(nums[i]+nums[j]>target)j--;
            //找到等于target的元素
            else{break;}
        }
        //再去原数组中找两个元素的下标
        for(int k=0;k<nums.length;k++ ){
            if(newNums[k]==nums[i]){
                i=k;
                break;
            }   
        }
        //防止两个相同元素时从头到尾找,找到同一个元素
        for(int m=nums.length-1;m>=0;m-- ){
            if(newNums[m]==nums[j]){
                j=m;
                break;
            }
        }
        int[]anr=new int[]{i,j};
        return anr;
    }
}

时间复杂度:Arrays.sort()使用的是归并排序的时间复杂度为n*log(n)。
空间复杂度:O(n)
hash法:
什么时候使用哈希法?当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。本题就需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是是否出现在这个集合,那么我们就应该想到使用哈希法了。
本题不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
使用数组和set来做哈希法的局限:
数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
此时就要选择另一种数据结构:map ,map是一种key-value的存储结构,可以用key保存数值,用value再保存数值所在的下标。
map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。

public int[] twoSum(int[] nums, int target) {
    int[] res = new int[2];
    if(nums == null || nums.length == 0){
        return res;
    }
    Map<Integer, Integer> 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;
}

时间复杂度: O(n)
空间复杂度: O(n)
本题其实有四个重点:
为什么会想到用哈希表?(需要遍历数组找到两个数,且相加之和等于target)
哈希表为什么用map?(map可以存key和value值,因为本题返回的是原数组的元素的下标,这也是我自己方法中为什么需要新的数组来存排序前数组的原因)
本题map是用来存什么的?(存key和value值,并利用map可以快速查找某个元素是否存在来寻找当前元素与target的差值是否存在)
map中的key和value用来存什么的?(key存访问过的元素,value存下标)
把这四点想清楚了,本题才算是理解透彻了。

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值