Leetcode刷题--哈希表

基础知识

哈希表: 数组 + 链表

作用:能快速判断一个元素是否存在一个集合中。 时间复杂度是: O(1)。

原理:因为使用了哈希算法,可以计算key的哈希值,然后映射到链表中,通过数组下标快速的访问到这个元素。 最简单的映射关系: 下标 = hash值 % tableSize

问题:会出现Hash冲突,就是计算不同的key得到同一个值,放到数组的同一个下标,即发生了Hash冲突。

解决方案:

(1)拉链法:冲突的元素直接变量链表。这个要防止链表的长度太长了,影响查询的效率。

(2)线性探测法: 冲突的时候,将计算的Hash值继续往下排列,移到数组的下一个下标位置,不形成链表。 这个要要数组的大小大于总元素的个数。

类型一 数组也是特殊的哈希表

第1题 : 242. 有效的字母异位词

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

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

题目翻译: 这个题翻译说来就是看这两个字符串的字符是不是全部都相同,个数也都必须全部相同。

思路: (1)直接使用HashMap是最简单的想法。
(2)数组其实也是特殊的 哈希表。因为只有26个小写字符,数组的大小就定义为26,一个很关键的问题是怎么把 字符 映射到对应的数组下标,比如:a 就是 0。

题解
暴力使用HashMap:

class Solution {
    public boolean isAnagram(String s, String t) {
        //这里就是判断t和s的字符是不是完全相等
        //HashMap把t和s的字符统计
        HashMap<Character,Integer> sMap = new HashMap<>();
        HashMap<Character,Integer> tMap = new HashMap<>();

        for(int i = 0; i < s.length(); ++i){
            sMap.put(s.charAt(i), sMap.getOrDefault(s.charAt(i), 0) + 1);
        }

        for(int i = 0; i < t.length(); ++i){
            tMap.put(t.charAt(i), tMap.getOrDefault(t.charAt(i), 0) + 1);
        }

        //遍历tMap,看是否所有字符都和s的相同
        for(Character cur : tMap.keySet()){
            if(!tMap.get(cur).equals(sMap.get(cur))){
                return false;
            }else{
                sMap.remove(cur);
            }
        }

        if(sMap.size() != 0){
            return false;
        }

        return true;
    }
}

数组形式:

class Solution {
    public boolean isAnagram(String s, String t) {
        //数组其实也是 哈希表, 小写字母只有26个
        //s字符的时候++,t字符的时候--,最后判断是不是所有位置都为0即可
        int[] arr = new int[26];

        //处理s字符串 --> 注意字符和数组下标的映射,使用 s.charAt(i) - 'a'
        for(int i = 0; i < s.length(); ++i){
            arr[s.charAt(i) - 'a']++;
        }

        //处理t字符串
        for(int i = 0; i < t.length(); ++i){
            arr[t.charAt(i) - 'a']--;
        }

        //看数组是不是都为0
        for(int i = 0; i < arr.length; ++i){
            if(arr[i] != 0){
                return false;
            }
        }

        return true;
    }
}

第2题: 383. 赎金信

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

题目翻译: 与上一题不同,这个题翻译说来就是看 magazine 的字符是不是能囊括 ransomNote 的所有字符。

思路: 直接使用数组 来代替 HashMap 完成。

题解

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        //这个就是看 magazine 的字符是不是包含  ransomNote  所有的字符
        //数组也是特殊的哈希表
        int[] arr = new int[26];

        //处理字符串magazine
        for(int i = 0; i < magazine.length(); ++i){
            arr[magazine.charAt(i) - 'a']++;
        }

        //处理字符串ransomNote
        for(int i = 0; i < ransomNote.length(); ++i){
            arr[ransomNote.charAt(i) - 'a']--;
        }

        //看数组里面所有的是不是都 > 0
        for(int i = 0; i < arr.length; ++i){
            if(arr[i] < 0){
                return false;
            }
        }

        return true;
    }
}

上面的最后两个for循环可以放在一起进行处理:

//处理字符串ransomNote
        for(int i = 0; i < ransomNote.length(); ++i){
        	if(arr[ransomNote.charAt(i) - 'a'] > 0){
				arr[ransomNote.charAt(i) - 'a']--;
			}else{
				return false;
			}
        }

第3题: 49. 字母异位词分组

题目描述
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。

分析: 这题是难题较大的一题,我认为关键的问题是 : 有这么多的字符串,要怎么确定这些字符串会是字母异位词,若直接使用HashMap<Character, Integer>,显然不现实,因为这么多字符串,每一个都要进行处理,你会得到很多的HashMap,你要对不同的map去判断他们的字符数是不是相同,感觉起来就是非常不靠谱的事。
字母异位词,我们要深究他的概念,如果是字母异位词,那么所有字符数都是相等的。这样我们是不是可以使用对字符串进行排序来判断,不需要字符的个数。 将排序后的字符串作为Mapkey,而将结果List<String>作为value

思路: 使用HashMap。

题解

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        //使用HashMap,关键问题是怎么确定 这个字符串和其他的字符串是字母异位词
        //如果HashMap的key使用Character,这样肯定会特别麻烦
        //另外,我们应该考虑到对字符串进行排序,这样如果是字母异位词,排序后肯定是相同的。
        //将HashMap设计成HashMap<String, List<String>>

        //总结果
        List<List<String>> res = new ArrayList<>();

        //创建HashMap
        HashMap<String, List<String>> map = new HashMap<>();

        //遍历字符数组
        for(String str : strs){
            //字符串转成字符数组,方便排序
            char[] strChar = str.toCharArray();
            //排序
            Arrays.sort(strChar);
            //得到排序后字符串  --> 这里要使用new String(),不能使用toString(),因为没有重写
            String newStr = new String(strChar);

            //拿排序后的字符串去map中查看--> 如果这个字符串是第一次,就创建出一个
            List<String> sList = map.getOrDefault(newStr, new ArrayList<String>());
            //然后把 未排序的字符串加入,并放入map中
            sList.add(str);
            map.put(newStr, sList);
        }

        //处理map,变成我们的结果
        for(Map.Entry<String, List<String>> entry : map.entrySet()){
            res.add(entry.getValue());
        }

        return res;
    }
}

上面存在一些巧妙处理的地方,可以多消化消化。

还有遍历map的几种方式也要掌握。除了上面这样方式,还有一种较为常见的方式,使用map.keySet(),得到所有的key集合。

第4题: 438. 找到字符串中所有字母异位词

题目描述
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)

分析: 这题和上面一题非常像。这是这里变成了字符串,并且要返回的是满足条件的下标开始位置。

思路: (1)通过截取字符串暴力。
(2)可以使用数组模拟哈希表,然后采用滑动窗口的方式求解。

题解
暴力方式

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        //这题和 49.字母异位词分组  非常像
        //可以对s字符串截取p长度

        //结果
        List<Integer> res = new ArrayList<>();
        //先对p进行排序
        char[] cp = p.toCharArray();
        Arrays.sort(cp);
        String newP = new String(cp);

        //字符串p的长度
        int pLen = p.length();
        //遍历字符串s,截取字符串进行判断
        for(int i = 0; i <= s.length() - pLen; ++i){
            //从s中截取子串
            String curS = s.substring(i, i + pLen);
            //排序
            char[] sp = curS.toCharArray();
            Arrays.sort(sp);
            if(newP.equals(new String(sp))){
                res.add(i);
            }
        }

        return res;
    }
}

使用数组的方式:

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        //滑动窗口方法
        int slen = s.length();
        int plen = p.length();
        List<Integer> result = new ArrayList<>();
        if(slen < plen) return result;
        //数组记录s和p字符出现的个数
        int[] sarr = new int[26];
        int[] parr = new int[26];
        //遍历p,把p加入到parr中,顺便处理s的前p个字符
        for(int i = 0; i < p.length(); ++i){
            parr[p.charAt(i) - 'a'] += 1;
            sarr[s.charAt(i) - 'a'] += 1;
        }
        //判断p的第一个长度是不是
        if(Arrays.equals(sarr, parr)){
            result.add(0);
        }
        //判断从0下标开始之后的,使用滑动窗口方法。i为右指针,i-plen为窗口的左指针
        //对p后面的每个字符进行遍历,每次循环的时候,将右指针的字符加上,同时左指针的字符减去,就相当于是一个滑动窗口
        for(int i = plen; i < slen; ++i){
            sarr[s.charAt(i) - 'a'] += 1; //加上前一个字符
            sarr[s.charAt(i-plen) - 'a'] -= 1; //减去最后一个字符
            if(Arrays.equals(sarr,parr)){
                result.add(i - plen + 1);
            }
        }
        return result;
    }
}

第5题 : 349. 两个数组的交集

题目描述
给定两个数组,编写一个函数来计算它们的交集。

题目翻译: 找到他们公有的元素,并且不重复。

思路: 使用HashSet就没错。

题解

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        //要去重,判断一个元素是不是再两个数组里面都有
        //使用HashSet最为合适
        HashSet<Integer> set1 = new HashSet<>();
        for(int num : nums1){
            set1.add(num);
        }

        //存储结果
        HashSet<Integer> res = new HashSet<>();
        for(int num : nums2){
            if(set1.contains(num)){
                res.add(num);
            }
        }

        int[] ans = new int[res.size()];
        int count = 0;
        for(Integer num : res){
            ans[count++] = num;
        }

        return ans;

    }
}

第6题 : 350. 两个数组的交集 II

题目描述
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

题目翻译: 和上题的区别是: 这里的结果不能去重。

思路: 因为不能去重,就不能使用HashSet了,要使用HashMap。

题解

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        //与349题非常类似,但是这里的结果是不能去重的,那就使用HashMap
        HashMap<Integer,Integer> map1 = new HashMap<>();
        for(int num : nums1){
            map1.put(num, map1.getOrDefault(num, 0) + 1);
        }

        //结果集
        List<Integer> res = new ArrayList<>();
        //遍历第二个数组,在map1存在就加入res,map1--。
        for(int num : nums2){
            //存在才需要处理,不存在直接跳过
            if(map1.containsKey(num)){
                //放入
                res.add(num);
                //map1--
                map1.put(num, map1.get(num) - 1);
                //已经没了
                if(map1.get(num) == 0){
                    map1.remove(num);
                }
            }
        }

        //遍历res
        int[] ans = new int[res.size()];
        for(int i = 0; i < res.size(); ++i){
            ans[i] = res.get(i);
        }

        return ans;

    }
}

此外,还可以使用排序 + 双指针的方式。

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        //使用双指针法,  但是要提前进行排序
        Arrays.sort(nums1);
        Arrays.sort(nums2);

        int len1 = nums1.length;  int len2 = nums2.length;
        //遍历下标
        int index1 = 0; int index2 = 0; int index = 0;
        //创建结果数据,以小的为大小
        int[] res = new int[len1 > len2 ? len2 : len1];

        while(index1 < len1 && index2 < len2){
            if(nums1[index1] < nums2[index2]){
                index1++;
            }else if(nums1[index1] > nums2[index2]){
                index2++;
            }else{
                //相等的情况
                res[index++] = nums1[index1];
                index1++;
                index2++;
            }
        }

        //从res中处理得到最终结果
        return Arrays.copyOfRange(res, 0, index);
    }
}

第7题 : 202. 快乐数

题目描述
编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果 可以变为 1,那么这个数就是快乐数。

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

思路: 要理解快乐数, 里面有一个点,会出现无限循环,无限循环就是会出现重复,重复就使用HashSet,可以这样: 只要 n != 1,就一直循环,直到找到1为止。 然后在循环里面去看这个n是否重复,不重复,就加入并更新n的值,重复那就是不限循环,return false
还有一个关键点是: 给你一个数,你怎么样能拿到这个数的所有位置上的数,这样要掌握。

题解

class Solution {
    public boolean isHappy(int n) {
        //如果出现无限循环,那就是结果会重复,重复就用HashSet
        //还有一个关键是:  给你一个数,你怎么获得这个数每个位置上的数值分别是什么
        HashSet<Integer> set = new HashSet<>();

        //只要不是1就一直找
        while(n != 1){
            //看是否n重复了 --> 无限循环
            if(set.contains(n)){
                return false;
            }
            //加入
            set.add(n);
            //没有重复,要处理这个n,拿到所有位置的数
            //记录n每个位置数平方和后的结果
            int temp = 0;
            while(n > 0){
                //循环取每一位数值, 从 个位 -> 十位 -> 百位
                int num = n % 10;
                temp += num * num;
                //更新n,才能取到下一位的
                n = n / 10;
            }
            //进行下一轮循环
            n = temp;
        }

        //n == 1
        return true;
    }
}

关键:给你一个数,怎么获取 个位、十位、百位。。。上的每一个数值大小。

/*
 * 这个num就是不断更新,从 个位 -> 十位 -> 百位。。一直取,直至取完为止
 */
while(n > 0){
	//循环取每一位数值, 从 个位 -> 十位 -> 百位
	int num = n % 10;
	//更新n,才能取到下一位的
	n = n / 10;
}

第8题 : 1. 两数之和

题目描述
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

思路
(1)两层for暴力。
(2)使用HashMap,可以记录元素的下标位置。

题解

class Solution {
    public int[] twoSum(int[] nums, int target) {
        //使用HashMap,因为要返回的是数组的下标位置
        HashMap<Integer,Integer> map = new HashMap<>();

        for(int i = 0; i < nums.length; ++i){
            int cur = nums[i];
            //找到
            if(map.containsKey(target - cur)){
                return new int[]{i, map.get(target - cur)};
            }else{
                map.put(cur, i);
            }
        }

        //没有
        return new int[0];
    }
}

第9题 : 454. 四数相加 II

题目描述
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

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

思路
使用组合的方式,有4个不同的数组,将两两数组进行组合,1和2的sum组合放到map中,然后去3和4的sum中找是否存在让四个数的结果为0的。

题解

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        //使用HashMap对四个数组进行两两分组。 要记录次数
        HashMap<Integer,Integer> map = new HashMap<>();
        //1和2的结果分为一组,放到map中,然后查看3和4中是不是能找到map中对应的数
        for(int num1 : nums1){
            for(int num2 : nums2){
                //和加入map
                int sum = num1 + num2;
                map.put(sum, map.getOrDefault(sum, 0) + 1);
            }
        }

        //记录结果
        int res = 0;
        //3和4分成一组
        for(int num3 : nums3){
            for(int num4 : nums4){
                int sum = num3 + num4;
                if(map.containsKey(0 - sum)){
                    res += map.get(0 - sum);
                }
            }
        }

        return res;
    }
}

第10题 : 15. 三数之和

题目描述
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组

思路
使用三指针法。 一个指针下标i去遍历数组,这是第一个数。 下标i+1作为左边界,下标length-1作为右边界,他们分别是第二个和第三个数。 然后根据sum的值不断去更新这些下标。这就是三指针法。
一个关键的问题是: 要去重!!!

题解

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //三指针法。 外层for从前向后遍历,然后下标从前往后,另外一个下标从后往前,要考虑去重问题
        List<List<Integer>> res = new ArrayList<>();
        //先排序  --> 排序是为了去重做准备
        Arrays.sort(nums);

        //遍历
        for(int i = 0; i < nums.length - 2; ++i){
            //sum肯定 > 0
            if(nums[i] > 0){
                return res;
            }

            //去重
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }

            //另外两个下标
            int left = i + 1, right = nums.length - 1;
            while(left < right){
                //三数之和
                int sum = nums[i] + nums[left] + nums[right];
                if(sum < 0){
                    left++;
                }else if(sum > 0){
                    right--;
                }else{
                    //和为0,那么第一次出现的就加入,在下面进行去重操作
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));

                    //left下标的去重
                    while(left < right && nums[left] == nums[left+1]){
                        left++;
                    }

                    //right下标的去重
                    while(left < right && nums[right] == nums[right-1]){
                        right--;
                    }

                    //更新left和right的下标
                    left++;
                    right--;
                }
            }

        }
        return res;
    }
}

第11题 : 18. 四数之和

题目描述
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • a、b、c 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案

思路
这个和上一题基本一样,就是多了一个数,所以将问题变成和上面一样就解决了。这样,我们就可以使用四指针法,然后和上面一样的方式解决问题。

题解

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        //可以这题处理成  第15题. 三数之和  类似的问题。  就是要先处理好两个数的和,这样就变成了15题。
        List<List<Integer>> res = new ArrayList<>();
        //排序,方便去重
        Arrays.sort(nums);

        for(int i = 0; i <= nums.length - 4; ++i){
            //去重
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }
            for(int j = i + 1; j <= nums.length - 3; ++j){
                //去重
                if(j > i + 1 && nums[j] == nums[j-1]){
                    continue;
                }

                //计算出3和4的和应该是多少
                int sum = target - nums[i] - nums[j];  

                //另外两个下标
                int left = j + 1, right = nums.length - 1;
                while(left < right){
                    int sumlr = nums[left] + nums[right];
                    if(sum > sumlr){
                        //和太小了
                        left++;
                    }else if(sum < sumlr){
                        right--;
                    }else{
                        //相等,加入,然后去重
                        res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));

                        //left去重
                        while(left < right && nums[left] == nums[left+1]){
                            left++;
                        }

                        //right去重
                        while(left < right && nums[right] == nums[right-1]){
                            right--;
                        }

                        //更新坐标
                        left++;
                        right--;
                    }
                }
            }
        }

        return res;
    }
}

总结

(1)数组是特殊的哈希表,如果个数确定,就可以使用数组代替哈希表。
(2)如果有去重操作,那就使用HashSet; 如果不需要去重,并且还要将所有的结果都进行展示,那就使用HashMap

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值