LeetCode-哈希专题

        本文声明:本文参考【代码随想录】的文章,思路以及代码。基于他的文章(希望大家直接去看原作者的优秀文章【代码随想录】,这篇博客仅为了记录自己的学习过程),形成了自己的理解,希望总结成自己的经验与知识,于是发表了这篇博客,如有不妥的地方欢迎大家指正。

一,哈希表法

        1,哈希表:

        哈希表是什么:哈希表是构造出来的一种可以快速查找的数据结构,是按值存储。(不详细讲哈希函数,处理冲突的方法,负载因子等等知识)。

        2,应用场景:

        一般哈希表都是用来快速判断一个元素是否出现集合里。(这在Java中用Set集合实现)

        提供一种高效的方法来存储和检索键/值对,以便快速查找值(Java中的Map集合实现)

        3,注意事项:

        Java中,哈希表体系:不单单是指 map。而是包含了Set集合, List列表和Map键值对, 以及特殊情况下还有数组(个人理解)。具体如何进行选择合适的数据结构,多刷题多总结。Set集合,List集合和Map集合的特点:

        Set代表的是无序的,不能重复的集合;

        List代表的有序,可以重复的集合;

        Map代表的具有映射关系的集合。

二,哈希的算法思想解题

1,题解:力扣242.有效的字母异位词  

题目描述:   题目:力扣(LeetCode)官网 - 有效的字母异位词     

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

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

                示例 1:

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

                示例 2:

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

题解思路:

        需要统计s和t各个组成字母出现的次数, 这就需要使用Map键值对来统计了。优化1:只需要一个Map对象:其中对s的处理:出现字母对应位置的值+1;    其中对t的处理:出现字母对应位置的值-1;最后判断map中值是都均为0。优化2:map的键是确定了的,最多26个小写字母,因此本题可用数组代替map,(tip:map涉及位置的计算,耗时的;而数组可用直接定位)

        多练习,多总结,判断那种情况可用使用数组代替Map进行处理,以提升性能,计算速度

  解题代码:

/*
    题解:
        Java中s.charAt(index)用于提取字符串s中的特定字符操作
            charAt()方法可以用于提取字符串中特定位置的字符。要使用该方法,必须向其传递一个参数,该参数为字符串中特定字符的位置。注意,Java中的字符串索引从0开始,因此字符串的第一个字符的索引为0,第二个字符的索引为
        
        s.toCharArray():可以将一个字符串转换成一个字符(char)数组。它的作用是将字符串中的每个字符分离出来,存放在一个新的数组中,并返回这个数组。

        Arrays类中包含各种用于操作数组的方法,下面是比较常用的几种方法:
            1.Arrays.toString():用于返回指定数组内容的字符串表示形式
            2.Arrays.fill():用于替换数组原元素 
            3.Arrays.sort():用于按照数字顺序排列指定的数组(默认升序)
            4. Arrays.equals():用于比较两个数组内容是否相等,返回类型为Boolean,两数组内容相同返回true,不同返回false
            5.Arrays.binarySearch():用于查找数组中的元素,返回值为int
            6.Arrays.copyOf() 和Arrays.copyOfRange():Arrays.copyOf() 
            具体的自己去API文档学习,使用规则和细节。


    自己写的:
        //得使用map键值对 but这里有个小tip,s和t都是由字母组成得,那么就可以用数组来代替map
        int[] count = new int[26];  //26个小写字母,初始化为0哦

        for(int i = 0; i < s.length(); i++){
            count[s.charAt(i) - 'a']++;    
        }

        for(int i = 0; i < t.length(); i++){
            int target = t.charAt(i) - 'a';
            count[target]--;    
        }

        for(int i = 0; i < 26; i++){
            if(count[i] != 0){
                System.out.println("false");
                return false;
            }
        }
        System.out.println("true");
        return true;

*/
class Solution {
    public boolean isAnagram(String s, String t) {
        //大佬版
        //法二:排序,然后判断是否一样
        if(s.length() != t.length()){   //求字符串长度length()
            return false;
        }

        char[] str1 = s.toCharArray();
        char[] str2 = t.toCharArray();
        Arrays.sort(str1);
        Arrays.sort(str2);
        
        return Arrays.equals(str1,str2);

    }
}

2,题解:力扣349. 两个数组的交集

题目描述:题目:力扣(LeetCode)官网 - 两个数组的交集

        给定两个数组 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] 也是可通过的

解题思路:

        本题很明显的是:判断一个一个元素是否在集合之中,因此利用哈希来做,这里选用Set集合来做。具体做法:1,设置一个集合Set集合coll 存入nums1的所有元素,Set集合result存储最终结果;2,遍历nums2的元素判断是否在coll之中(求元素的交);3,若在,则加入result中,因为采用的是Set集合result,会去除重复元素,以达到题目要求:输出结果每个元素唯一。

        本题1是意识到用Map进行解题;2,是利用Set集合特性,选择HashSet数据类型。

解题代码: 

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        
        HashSet coll = new HashSet();
        HashSet result = new HashSet();
        //先把nums1存进去, HasSet会去重
        for(int i = 0; i < nums1.length; i++){
            coll.add(nums1[i]);
        }
        for(int j = 0; j < nums2.length;j ++){  //可以用for each来
            if(coll.contains(nums2[j])){
                result.add(nums2[j]);
            }
        }
        System.out.println(result);
        Object[] object = result.toArray();  //hashSet.toArray()只能得到Object[]数组

        int[] num = new int[object.length];     //还需要进一步转换为整形数组
        for(int i = 0; i < object.length;i++){
            num[i] = (int) object[i];
        }
        return num;

    }
}
3,题解:力扣202题. 快乐数

题目描述: 题目:力扣(LeetCode)官网 -快乐数    

      编写一个算法来判断一个数 n 是不是快乐数。「快乐数」 定义为:

                对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。

                然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。

                如果这个过程 结果为 1,那么这个数就是快乐数。

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

        示例 1:

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

        示例 2:

        输入:n = 2
        输出:false

解题思路:

        本题的功能实现:求平方和、判断是否快乐数等都还好。难点在于如何处理无限循环:如何让while结束。思路是:利用哈希Se集合,储存每一个处理过的数字。倘若n后续的平方数 又出现在Set集合中,则他是会无限循环的(直接返回false,结束while)。 

解题代码:

class Solution {
    public boolean isHappy(int n) {

        //思考点:怎么结束无限循环:答当sum又等于已经出现过的n时,就会产生循环
        //当然还有可能存在:无限不循环的情况。(Why) 默认不考虑出现不了
        //自己写的  SO 应当把已经检验过的n值,存起来
        Set<Integer> set = new HashSet<Integer>();    //不要求排序,但是集合内无重复元素
        while(!set.contains(n)){
            set.add(n);
            n = squareSum(n);
            if(n == 1){
                return true;
            }
        }
        return false;

        //二选一,注释掉其中一个

        //大老版:快慢指针。 真的是把快慢指针的精髓都运用的得心应手啊
        /*
            如果 n 是一个快乐数,即没有循环,那么快跑者最终会比慢跑者先到达数字 1。
            如果 n 不是一个快乐的数字,那么最终快跑者和慢跑者将在同一个数字上相遇。
        */
        int slow = n, fast = n;
        do{
            slow = squareSum(slow);    //走一步
            fast = squareSum(fast);
            fast = squareSum(fast);     //fast走两步
        }while(slow != fast);   //如果是快乐数,最后fast = 1 会等slow

        return slow == 1;   //return slow == 1 ? true : false;

        
    }

    public int squareSum(int n){
        int sum = 0;
        int temp;
        while(n != 0){
            temp = n % 10;
            sum = sum + temp * temp;
            n = n / 10; //两个整数相除,结果为整数
        }
        return sum;  
    }  
}
4,题解:力扣1,两数之和

题目描述:  力扣(LeetCode)官网 -两数之和

        给定一个整数数组 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]

解题思路:

  • 很明显直接两个for的暴力解:代码如下:
/*
        int[] result = new int[2];
        //直接干就完啦,
        for(int i = 0; i < nums.length - 1; i++){
            //这里也可以就是nums.length, 当就= length时直接不执行第二个for
            int temp = target - nums[i];
            for(int j = i + 1;j < nums.length;j++){
                if(nums[j] == temp){
                    result[0] = i;
                    result[1] = j;
                    return result;  //return new int[]{i,j};
                }
            }
        }
        return result;
        //O(n) = n^2 ; S(n) = 1.
*/
  • 代码优化:使用双指针可用减少一轮for。 这里使用map也能在一轮for中完成功能:

什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。

本题呢,就需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。那么我们就应该想到使用哈希法了。

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

注意Map与 数组和Set集合用法的区别、场景:数组的Set的局限性如下:

  • 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
  • set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。

解题代码:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        /*
            能够快速寻找数组中是否存在目标元素 --- 铁定哈希表
            使用哈希表,可以将寻找 target - x 的时间复杂度降低到从O(N) -> O(1)

            这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target -x, 然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。
        */
        Map<Integer, Integer> hashtable = new HashMap<Integer,Integer>();
        for(int i = 0; i < nums.length;i++){
            if(hashtable.containsKey(target - nums[i])){
                return new int[]{hashtable.get(target-nums[i]), i};
            }
            hashtable.put(nums[i], i); 
            //map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
        }
        return new int[0];
    }
}
5,题解:力扣四数相加II

题目描述:题目:力扣(LeetCode)官网 - 四数相加

给你四个整数数组 nums1nums2nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

        示例 1:

        输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
        输出:2
        解释:
        两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

        示例 2:

        输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
        输出:1

解题思路:

        利用题解4中的 Map思路: 定义map1存入nums1[v] + nums2[v](0 <= v, u < n);定义map2存入nums3[v] + nums4[v](0 <= v, u < n);键值对key-value:这里的key = num1[v] + nums2[u], value = key出现过的次数。最后在遍历map1和map2,相加是否等于0,统计符合要求的元组个数。

    自己写的:还是有很多不规范的地方,或者不懂的地方会出BUG
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        //两个两数之和
        HashMap hashMap1 = nums_add(nums1, nums2);   //nums1 + nums2
        HashMap hashMap2 = nums_add(nums3, nums4);  //nums3 + nums4

        int count = 0;
        Set<Integer> setKey1 = hashMap1.keySet();   
        //hashMap.keySet()返回是的Object类型的set集合, 需要用Integer类型接收,而不是int(个人理解Object类型可以转Integer类型,但不能转为int类型) 
        //知识点1,定义变量(Set, Map)是要用Integer代替int, :因为泛型不接受基本数据类型
        Set<Integer> setKey2 = hashMap2.keySet();

        for (Integer temp1 : setKey1){
            for( Integer temp2 : setKey2){
                if(temp1 + temp2 == 0){
                    count += (int)hashMap1.get(temp1) * (int)hashMap2.get(temp2);
                }
            }
        } 
        return count;
    }

    public HashMap nums_add(int[] nums1, int[] nums2){
        HashMap hashMap = new HashMap();   //nums1 + nums2
        int n = nums1.length;
        for (int i = 0; i < n; i++ ){
            for (int j = 0; j < n;j ++){
                int temp = nums1[i] + nums2[j];
                if(hashMap.containsKey(temp)){
                    hashMap.put(temp, (int)hashMap.get(temp) + 1);
                }else{
                    hashMap.put(temp, 1);
                }   
            }
        }
        return hashMap;
    }

        代码优化:完全可用效仿题解4:只定义一个map:存储nums1[v] + nums2[u](0 <= v,u<n)。之后直接判断 (- nums3[v] - nums4[u]) 在不在map中就完事了。这一减少了一个map和两个for:

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        //本题简单的一个点是: 不用考虑重复的元素这种复杂情况:否则就不适合用哈希表
        /*  
            法二:
            大佬版本:对自己代码的改进:
                对于nums3 , nums4不需要在开辟空间存hashMap2了
                直接在hashMap1中找key = 0 - nums3[i] - nums4[j]就行
        */
        Map<Integer, Integer> hashMap = new HashMap();
        for(int u : nums1){
            for(int v : nums2){     //数组也能用foreach
                hashMap.put(u+v, hashMap.getOrDefault(u+v, 0) + 1);
                System.out.println(hashMap.get(u+v));
                //nums1={1,2}, nums2={-2, -1}
                //get(u+v)= 1,1,2,1。结果又是对的:没有默认返回0,然后0+1 = 1Rightt!!!
            }
        }

    // 知识点2:hashMap.getOrDefault()函数:可用于简化代码(替换自己写的代码里的if- else)
    // (1)getOrDefault(key, default)如果存在key, 则返回其对应的value, 否则返回给定的默认值
    // (2)getOrDefault(key, default) +/-1:key值相同, value值+1或者-1;否则返回给定的默认值 +1或者-1
        int answer = 0;
        for(int i : nums3){
            for(int j : nums4){
                if(hashMap.containsKey(-i-j)){
                    answer += hashMap.get(-i-j);
                }

            }
        }
        return answer;
    }
}
6,题解:力扣赎金信

题目描述:题目:力扣(LeetCode)官网 -赎金信

        给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。

        示例 1:

        输入:ransomNote = "a", magazine = "b"
        输出:false

        示例 2:

        输入:ransomNote = "aa", magazine = "ab"
        输出:false

解题思路:

        这道题就是解题1的翻版: 题解1是判断两个字符串构成的字符集合相等与否;本题是判断字符串1构成的字符集合是否能满足字符串2构成所需的字符集合(判断包含关系)。本题只需意识到用Map集合来统计每个字符出现次数即可 ---> 可用使用数组代替map!!!

解题代码:

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        //典型的哈希表法,tip本题均为小写字母可用数组代替哈希表
        int[] character = new int[26];

        //重要知识点1:java 怎么从string里面获取其中的某一个字(字符) 
        //Java中的String类提供了charAt方法,它可以用于获取字符串中指定索引位置的字符。
        //      格式:char res = (String)array.charAt(int index);

        /*
            方法二:对于for循环可用用一下方法实现
            for(char c : maganize.toCharArray()){
                character[c- 'a']++;
            }

            知识点2:String array.toChararray():
                使用String类的toCharArray()函数将字符串转换为字符数组(多用多记啊)
        */

        for(int i = 0; i < magazine.length(); i++){
            int temp = magazine.charAt(i) - 'a';
            character[temp]++;
        }

        for(int i = 0; i < ransomNote.length(); i++){
            int temp = ransomNote.charAt(i) - 'a';
            character[temp]--;
        }

        for( int j : character){
            if(j < 0){
                return false;
            }
        }
        return true;
    }
}

小飞猪起飞楼!上岸上岸上岸。

        

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值