【Java】【python】【数据结构与算法】leetcode刷题记录--哈希表

简述

哈希是一种将大量信息压缩到较小空间的技术,本质上就是一种映射关系,将复杂的信息映射到对应的哈希表的位置,因此可以提供快速的查找。哈希会提供特点的映射函数(散列函数),例如有大小为5的哈希表,分别为0、1、2、3、4,而每个位置存储的值是模除5等于对应下标的值。例如5就存在0号单元,10也是。

具体来说,哈希函数可以接受任何类型的输入,并返回一个固定长度的数字。哈希函数的主要特性是,对于相同的输入,它总是返回相同的输出。同时,尽管不同的输入可能会产生相同的输出(这种情况称为哈希冲突),但是在理想情况下,哈希函数应该尽量使这种情况发生的概率最小。当然,哈希碰撞(两个不同的元素或键被哈希函数映射到同一位置)是可能发生的,但是Java的 HashSet 和 HashMap 已经内部处理了这个问题。当哈希碰撞发生时,它们会使用链表或红黑树(当链表长度超过一定阈值时)来存储同一哈希值的元素,从而保证查找的效率。

从上文得知,哈希为我们提供了快速的增删改查的操作,在使用相关的数据结构中,我们不需要考虑hash实际的实现是怎么样的。比如在Java中,有HashSet 和 HashMap 这两个基于哈希表的数据结构。这两个数据结构分别实现了set接口和map接口,前者是模拟数学中集合的概念,后者则是模拟键值对(类似于python中的字典)。而哈希的实现主要是提供快速(常数级时间复杂度)的查找、插入和删除操作。因此,作为使用者,你不需要关心哈希碰撞的问题,只需要知道 HashSet 和 HashMap 可以提供快速的查找、插入和删除操作就足够了。

适应场景

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景,也应该第一时间想到哈希法!

242 有效的字母异位词在这里插入图片描述

这题就是很明显的hash(我都想不出暴力怎么做),也就是用数组或者hashmap进行存储字符。首先,对于s的每个字符都当作键值存进hashmap,每遇到一个重复的就键值加1。然后就是对t做相同的遍历,但是如果键存在,键值就减一。当然,如果发现有不存在的键就返回false。最后是对整个hashmap进行遍历,如果有键的值不是0那就是false。代码如下(也可以用数组做,大同小异):

class Solution {
    public boolean isAnagram(String s, String t) {
        HashMap<Character, Integer> map = new HashMap<>();
        
        if(s.length()!=t.length()){
            return false;
        }
        int len = s.length();
        for(int i=0; i<len; i++){
            char tmp = s.charAt(i);
            if(!map.containsKey(tmp)){
                map.put(tmp,1);
            }
            else{
                map.put(tmp,map.get(tmp)+1);
            }
        }

        for(int i=0; i<len; i++){
            char tmp = t.charAt(i);
            if(!map.containsKey(tmp)){
                return false;
            }
            else{
                map.put(tmp,map.get(tmp)-1);
            }
        }

        for (Integer value : map.values()) {
            if(value != 0){
                return false;
            }
        }
        return true;
    }
}

349 两个数组的交集

在这里插入图片描述
显而易见,这个题可以用HashSet来做,第一个set用来存储nums1,第二个则用来筛选:如果nums2的值也在set1中,就可以存储到set2里,以此来筛选。

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        HashSet<Integer> set1 = new HashSet<>();
        HashSet<Integer> set2 = new HashSet<>();

        for(int i: nums1){
            set1.add(i);
        }

        for(int i:nums2){
            if(set1.contains(i)){
                set2.add(i);
            }
        }

        int[] res = new int[set2.size()];
        int cnt = 0;
        for(int i: set2){
            res[cnt++] = i; 
        }

        return res;
    }
}

当然,这题可以用数组来做,数组的优点就是更快,并且会省内存,这一点在数据量很大的时候有优势。代码上会略微麻烦一点。

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        int[] arr1 = new int[1005];
        int[] arr2 = new int[1005];
        int cnt=0;
        for(int i: nums1){
            arr1[i] = 1;
        }

        for(int i: nums2){
            if(arr1[i]==1&&arr2[i]!=1){
                arr2[i] = 1;
                cnt++;
            }
        }

        List<Integer> resList = new ArrayList<>();
        for(int i=0; i<arr2.length; i++){
            if(arr2[i]==1){
                resList.add(i);
            }
        }

        // 将ArrayList转换为int[]数组
        int[] resArray = new int[resList.size()];
        for (int i = 0; i < resList.size(); i++) {
            resArray[i] = resList.get(i);
        }

        return resArray;
    }
}

202 快乐数

在这里插入图片描述
这里一定要注意,一个数要么是变为1,要么就无限循环。用这个特性很好做出这题:一直循环处理,如果处理结果为1那就是快乐数,反之,记录在hashset中,如果在某次循环的结果在hash里存在,则不是快乐数。

class Solution {
    public boolean isHappy(int n) {
        HashSet<Integer> record = new HashSet<>();
        int sum=0;
        for(sum=0; true; sum=0){
            // 循环得到每位平方的和
            while(n!=0){
                int tmp = n%10;
                sum += tmp*tmp;
                n /= 10;
            }
 
            if(sum==1){
                return true;
            }
            else if(record.contains(sum)){
                return false;
            }
            else{
                record.add(sum);
                n = sum;
            }
        }
    }
}

1.两数之和

在这里插入图片描述
这题相对简单,用暴力做法也比较好做,直接使用双重循环查找即可。但如果用哈希那明显会更快捷,而快速判断元素是否存在,也是我们用hash的很适合的情况。
我们可以申请一个Map(或者字典),在map中,键是nums中的元素,而键值则是对应的下标(因为要返回下标)。从头开始查找,如果target减去当前的元素(nums[i])的结果是map的键的话,那就说明找到了,反之则说明没找到,需要添加键值对。例如对于第一个例子,查找到2时,map为空,则添加(2,0)的键值对。查到7时,target-7=2,而2是键,因此返回[i,value]即可。

import java.util.HashMap;

class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> map = new HashMap<>();

        for (int i = 0; i < nums.length; i++) {
        //如果相减得到的差是键则找到结果,返回数组
            if (map.containsKey(target - nums[i])) {
                return new int[]{i, map.get(target - nums[i])};
            } else {
                map.put(nums[i], i);
            }
        }
        
        // 如果没有找到匹配的数,返回null
        return null;
    }
}

python版本:

class Solution:
    def twoSum(self, nums, target):
        num_map = {}

        for i, num in enumerate(nums):
            if target - num in num_map:
                return [i, num_map[target - num]]
            num_map[num] = i
        
        # 如果没有找到匹配的数,返回None
        return None

enumerate 是 Python 内置函数,用于在遍历列表或其他可迭代对象时生成一个索引序列。它返回一个包含索引和值的元组,因此在循环中既可以获得元素的值,也可以获得元素的索引。这样使得代码简洁。

454. 四数相加 II

在这里插入图片描述
这个题不要怕麻烦,一开始我还想有没有一次就能做出来的方法,实际上不太行…
我们可以进行分组,nums1和2为一组,3和4为一组,先对1、2进行遍历,将其元素相加的结果作为键,如果是第一次记录这个键,则键值为1,否则则为已有键值加1,这样就可以得到nums1、2的一个哈希。
对于3、4也是这样的操作,如果3、4的元素的相反数存在在map里,则让res加上1、2的键值,这样的正确性是:假设1、2能得到-1这个值的情况有3种,而3、4得到1的情况有2种,那么得到0也就是这三种加这两种,即5种。
代码实现也相对简单,如果不知道getOrDefault,用if判断map.containsKey即可。

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int res=0;
        HashMap<Integer,Integer> map = new HashMap<>();

        for(int a:nums1){
            for(int b: nums2){
                map.put((a+b), map.getOrDefault((a+b), 0) + 1);
            }
        }

        for(int c:nums3){
            for(int d: nums4){
                if(map.containsKey(-(c+d))){
                    res += map.get(-(c+d));
                }
            }
        }

        return res;
    }
}

python版本:也可以不用defaultdict,而用if… in的格式来写代码。

class Solution:
    def fourSumCount(self, nums1, nums2, nums3, nums4):
        res = 0
        num_map = defaultdict(int)

        # 计算前两个数组的和及其频率
        for a in nums1:
            for b in nums2:
                num_map[a + b] += 1

        # 计算后两个数组的和及其相反数在num_map中的频率
        for c in nums3:
            for d in nums4:
                res += num_map[-(c + d)]

        return res

383 赎金信

在这里插入图片描述
一开始我看到这题,直接换成python写了一行代码:

return (ransomNote in magazine) 

确实过了一些样例,但会在aab,baa这个地方卡住…所以还是老老实实用hash。而这题的思路也非常简单,magazine的每个字符用Map存下,再去判断ransomNote的字符是否存在、存在的个数匹不匹配,进而得到答案:

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        HashMap<Character,Integer> map = new HashMap<>();

        for(int i=0; i<magazine.length(); i++){
            char tmp = magazine.charAt(i);
            if(!map.containsKey(tmp)){
                map.put(tmp,1);
            }else{
                map.put(tmp,map.get(tmp)+1);
            }
        }
        for(int i=0; i<ransomNote.length(); i++){
            char tmp = ransomNote.charAt(i);
            if(!map.containsKey(tmp)){
                return false;
            }else if(map.get(tmp)==0){
                return false;
            }else{
                map.put(tmp,map.get(tmp)-1);
            }
        }
        return true;
    }
}

通过很轻松,但我发现这题效率并不高。究其原因可能是数据结构比较复杂,hash在某些情况的查找的时间复杂度可能会退化,因此我们可以考虑用数组,毕竟只有26个字母。

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        // 假设所有字符都是小写字母
        int[] charCount = new int[26];

        // 统计magazine中每个字符的出现次数
        for (int i = 0; i < magazine.length(); i++) {
            charCount[magazine.charAt(i) - 'a']++;
        }

        // 检查ransomNote中每个字符是否可以由magazine提供
        for (int i = 0; i < ransomNote.length(); i++) {
            char tmp = ransomNote.charAt(i);
            if (charCount[tmp - 'a'] == 0) {
                return false;
            } else {
                charCount[tmp - 'a']--;
            }
        }

        return true;
    }
}

速度一下就快了很多。
python版本
当然,python的灵活度很高,也可以用别的方法来做,这里只是简单翻译一下。

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        char_count = [0] * 26

        for char in magazine:
            char_count[ord(char) - ord('a')] += 1

        for char in ransomNote:
            if char_count[ord(char)- ord('a')] == 0:
                return False
            else:
                char_count[ord(char)- ord('a')] -= 1
            
        return True

15 三数之和

leetcode链接:
在这里插入图片描述

public class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        
        if (nums.length < 3) {
            return res;
        } else if (nums.length == 3) {
            if (nums[0] + nums[1] + nums[2] == 0) {
                res.add(Arrays.asList(nums[0], nums[1], nums[2]));
                return res;
            } else {
                return res;
            }
        }
        
        Arrays.sort(nums);
        
        for (int i = 0; i < nums.length - 2; i++) { // 修正循环条件
            if (i > 0 && nums[i] == nums[i - 1]) continue; // 避免重复

            int left = i + 1;
            int right = nums.length - 1;
            
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                
                if (sum == 0) {
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    while (left < right && nums[left] == nums[left + 1]) left++; // 避免重复
                    while (left < right && nums[right] == nums[right - 1]) right--; // 避免重复
                    left++;
                    right--;
                } else if (sum < 0) {
                    left++;
                } else {
                    right--;
                }
            }
        }
        
        return res;
    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值