⑥ 哈希表

哈希表的数据结构

  • 数组(范围可控情况下使用)
  • set(数值较大情况下使用)
  • map(有键值对的情况下使用)

哈希表的应用场景:判断一个元素是否在集合中出现过。

242.有效的字母异位词

题目链接:https://leetcode.cn/problems/valid-anagram/

异位词定义:两个字符串的异位词等价于两个字符串排序后相等。

注意点:

两个字符串长度不相等直接返回flase

解法:

排序:对两个字符串进行排序,排序后判断字符串是否相等即可判断。

哈希表:定义一个能存放26个元素的哈希数组,统计字符串里面每一个字母出现的频率,然后在遍历第二个字符串的时候减去第二个字符串每个字母出现的频率,这个数组里面所有元素都是0的时候,说明两个字符串就是有效的字母异位词。

代码:

// 排序
class Solution {
    public boolean isAnagram(String s, String t) {
        if(s.length() != t.length()){
            return false;
        }
        char[] str1 = s.toCharArray();   // 将字符串转换为字符数组
        char[] str2 = t.toCharArray();
        Arrays.sort(str1);           // Arrays.sort()只能对数组进行排序
        Arrays.sort(str2);
        return Arrays.equals(str1,str2);
    }
}

// Hash法
class Solution {
    public boolean isAnagram(String s, String t) {
        if(s.length() != t.length()){   // 长度不同直接返回false
            return false;
        }
        // 定义一个长度为26的哈希数组,默认里面的值都为0
        int[] hash = new int[26];
        char[] str1 = s.toCharArray();   // 将字符串转换为字符数组
        char[] str2 = t.toCharArray();
        for(int i=0;i<s.length();i++){
            hash[str1[i]-'a']++;   // str1[i]-'a'得出相应的下标
        }
        for(int i=0;i<t.length();i++){
            hash[str2[i]-'a']--;
        }
        for(int i=0;i<26;i++){
            if(hash[i] != 0){
                return false;
            }
        }
        return true;
    }
}

349. 两个数组的交集

题目链接:https://leetcode.cn/problems/intersection-of-two-arrays/

注意点:

去重

不能直接用数组存储,数组需要提前定义长度,而结果的长度未知。

解题思路:

set方法:将nums1处理成哈希表的形式,nums2遍历每一个元素去查询在哈希表中是否出现过,如果有出现过,就将这个元素放进result集合中。

数组方法:定义一个比1000稍大的哈希数组,元素值默认为0,遍历nums1,以nums1中的元素值作为下标,遍历到的下标赋值为1,再遍历nums2,判断nums2中的元素有没有在哈希数组中出现过。出现过的元素就添加到哈希表中。

代码:

// 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<>();  // 存储nums1数组转成的哈希表
       Set<Integer> resSet = new HashSet<>();  // 存储结果的哈希表

       // 遍历数组1,将nums1中的元素存进哈希表set1中
       for(int i : nums1){
           set1.add(i);
       } 

       // 遍历数组2的过程中判断哈希表中是否存在该元素
       for(int i : nums2){
           if(set1.contains(i)){
               resSet.add(i);
           }
       }

        // 方法1:将结果集合转为数组
    //    return resSet.stream().mapToInt(x -> x).toArray();

        // 方法2:另外申请一个数组存放resSet中的元素,最后返回数组
        int[] arr = new int[resSet.size()];
        int j=0;
        for(int i : resSet){
            arr[j++] = i;
        }
        return arr;
    }
}

// 数组解法(测试数据<1000)
class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        int[] hash = new int[1005];   // 存放num1的哈希数组
        Set<Integer> resSet = new HashSet<>();  // 存放结果的哈希表,不用提前定义长度
        for(int i=0;i<nums1.length;i++){
            hash[nums1[i]] = 1;   // 以nums1中的元素值作为下标,遍历到的下标赋值为1
        }
        for(int i=0;i<nums2.length;i++){
            if(hash[nums2[i]] == 1){
                resSet.add(nums2[i]);
            }
        }
        return resSet.stream().mapToInt(x -> x).toArray();
    }
}

202.快乐数

题目链接:https://leetcode.cn/problems/happy-number/

注意点:

无限循环,求和的过程中,sum会重复出现。

解题思路:

定义一个函数,得到n拆分开来计算的结果,用哈希表set存储结果,循环判断结果是否等于1并且在不在哈希表中,false则添加还结果并继续计算,true则返回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 sum = 0;
        while(n > 0){    
            int temp = n%10;   // 得到个位数
            sum += temp*temp;
            n = n/10;   // 得到去掉个位数的下一个值
        }
        return sum;
    }
}

1.两数之和

题目链接:https://leetcode.cn/problems/two-sum/

注意点:

判断这个元素是否出现过或者这个元素是否在这个集合中出现过的时候,用哈希表做处理。

本题不要求按顺序返回答案

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

解题思路:

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

代码:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] result = new int[2];
        if(nums == null || nums.length == 0){
            return result;
        }

        // 暴力破解法
        // for(int i=0;i<nums.length-1;i++){
        //     for(int j = i+1;j<nums.length;j++){
        //         if(nums[i] + nums[j] == target){
        //             result[0] = i;
        //             result[1] = j;
        //         }
        //     }
        // }
        
        // 哈希表map解法
        Map<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<nums.length;i++){
            int temp = target - nums[i];  // 记录另一个要查找的元素
            if(map.containsKey(temp)){
                result[1] = i;
                result[0] = map.get(temp);
                break;
            }
            map.put(nums[i],i); // 未找到匹配的值则添加当前元素
        }

        return result;
    }
}
<think> 我们首先需要解析给定的代码。代码的功能是统计一组二进制字符串中每个位置上1出现的次数,然后根据条件输出结果。 代码逐句解析如下: 1. `from collections import Counter;` 这行代码从Python的collections模块导入Counter类。Counter是一个字典子类,用于计数可哈希对象。 2. `team = input().split(',');` 这行代码读取用户输入,并将输入的字符串按逗号分割成一个列表。例如,如果输入"101,111,000",则分割后得到['101','111','000']。 3. `cnt = Counter();` 这行代码创建了一个空的Counter对象,命名为cnt。Counter对象用于计数,这里我们准备用它来统计每个位置(索引)上出现'1'的次数。 4. `for person in team:` 这是一个循环,遍历列表team中的每一个字符串(每个人代表的二进制字符串)。 5. ` for i, num in enumerate(person):` 这是一个嵌套循环,遍历当前字符串person中的每一个字符(0或1),同时使用enumerate函数获取字符的索引i和字符本身num。 6. ` if num == '1':` 判断当前字符是否为'1'。 7. ` cnt[i] += 1` 如果当前字符是'1',那么在cnt这个计数器中,将位置i的计数加1。注意,Counter在访问不存在的键时会返回0(不会抛出KeyError),因此可以直接使用`+=1`。 8. `print(0) if len(cnt) < 5 else print(min(cnt.values()))` 这是一个条件表达式。首先,`len(cnt)`表示cnt中统计的位置个数(即有多少个不同的索引)。如果位置个数小于5,则打印0;否则,打印cnt中所有计数值的最小值(即出现次数最少的位置的1的个数)。 重点解析`cnt = Counter()`: - 这里创建了一个空的计数器对象。Counter是dict的一个子类,用于计数可哈希对象。它存储的是键值对,键是元素(这里我们使用的位置索引i),值是该元素的计数。 - 当我们执行`cnt[i] += 1`时,如果键i还不存在,那么Counter会自动将其初始化为0,然后再加1。这避免了普通字典需要先检查键是否存在或使用`defaultdict`的麻烦。 示例运行: 假设输入:"10101,11100,11010" 分割后:team = ['10101','11100','11010'] 然后遍历每个字符串: 第一个字符串'10101': 位置0: '1' -> cnt[0]=1 位置1: '0' -> 不增加 位置2: '1' -> cnt[2]=1 位置3: '0' -> 不增加 位置4: '1' -> cnt[4]=1 第二个字符串'11100': 位置0: '1' -> cnt[0]=2 位置1: '1' -> cnt[1]=1 位置2: '1' -> cnt[2]=2 位置3: '0' -> 不增加 位置4: '0' -> 不增加 第三个字符串'11010': 位置0: '1' -> cnt[0]=3 位置1: '1' -> cnt[1]=2 位置2: '0' -> 不增加 位置3: '1' -> cnt[3]=1 位置4: '0' -> 不增加 此时cnt的内容为:{0:3, 1:2, 2:2, 4:1, 3:1}(注意键是位置索引,值是该位置出现1的次数)。 len(cnt) = 5(因为有5个位置:0,1,2,3,4),所以输出min(cnt.values())=1(最小值为1)。 注意:这个代码假设每个输入的字符串长度相同吗?实际上,代码并没有做这个假设。如果输入的字符串长度不同,那么位置索引i可能会不同(比如第一个字符串长度为5,第二个字符串长度为3,那么第二个字符串只会遍历索引0,1,2)。因此,如果字符串长度不一致,那么不同字符串的相同位置索引可能代表不同的实际位置(比如第一个字符串的索引3和第二个字符串的索引3可能不是同一个位置)。不过,题目中并没有明确说明,但根据上下文,我们通常假设这些二进制字符串是等长的,表示同一组特征(每个位置代表同一特征)。 但是,在问题中没有明确说明等长,所以代码实际上可以处理不等长的情况。不过,在统计时,不同字符串的相同索引位置会被认为是同一个位置(例如,第一个字符串索引0和第二个字符串索引0都是第0位)。因此,如果字符串不等长,那么较短的字符串不会对后面位置进行统计(比如一个字符串长度为3,那么它不会统计索引3及以后的位置)。 最后,条件判断:如果统计的位置个数(即不同索引的数量)小于5,则输出0;否则输出所有位置中1出现次数的最小值。 这个条件可能源于具体的问题背景(比如要求至少5个位置才有意义,否则输出0)。 总结:这段代码统计了输入的一组二进制字符串中每个位置上1出现的次数,并输出最小出现次数(当位置个数不少于5时),否则输出0。 现在,根据要求生成相关问题。 </think>### Python代码逐句解析:`cnt = Counter()` 以下是对给定代码的逐句解析,重点说明 `cnt = Counter()` 的作用和实现原理: ```python from collections import Counter; # ① 导入Counter类 team = input().split(','); # ② 读取并分割输入 cnt = Counter(); # ③ 创建计数器对象(重点解析) for person in team: # ④ 遍历每个字符串 for i, num in enumerate(person): # ⑤ 遍历字符串的每个字符和索引 if num == '1': # ⑥ 检查字符是否为'1' cnt[i] += 1; # ⑦ 对应索引位置计数+1 print(0) if len(cnt) < 5 else print(min(cnt.values())) # ⑧ 条件输出结果 ``` #### 重点解析 `cnt = Counter()` 1. **功能作用** - 创建空的 `Counter` 对象,用于统计每个索引位置出现 `'1'` 的次数 - 本质是**字典的子类**,键为索引位置(整数),值为该位置出现 `1` 的累计次数 - 示例:若索引 `2` 出现 `3` 次 `1`,则存储为 `cnt = {2: 3}` 2. **底层原理** - **自动初始化**:当访问不存在的键(如新索引)时,自动初始化为 `0`,避免 `KeyError` - **高效计数**:基于哈希表实现,读写操作时间复杂度 $O(1)$ - 等效于普通字典的 `cnt = {}` + 手动初始化,但更简洁安全 3. **与普通字典的区别** ```python # 普通字典需手动处理缺失键 cnt_dict = {} if index not in cnt_dict: cnt_dict[index] = 0 cnt_dict[index] += 1 # Counter 自动处理 cnt_counter = Counter() cnt_counter[index] += 1 # 自动初始化缺失键 ``` #### 完整代码工作流程 1. **输入处理** `input().split(',')` 将输入按逗号分割,如 `"101,110"` → `["101", "110"]` 2. **统计阶段** - 遍历每个二进制字符串(如 `"101"`) - 对每个字符检查:`num == '1'` 时,在 `cnt` 中增加对应索引的计数 - 例如 `"101"` 会使索引 `0` 和 `2` 的计数 `+1` 3. **输出逻辑** - `len(cnt) < 5`:统计的索引位置不足 5 个时输出 `0` - 否则输出最小计数值 `min(cnt.values())` (即所有位置中出现 `1` 最少次数) #### 示例演示 输入:`"10101,11100,11010"` 处理过程: 1. 分割:`["10101", "11100", "11010"]` 2. 统计索引计数(结果): ``` 索引0: 3次 (所有字符串第0位都是1) 索引1: 2次 (第2个字符串为0) 索引2: 2次 (第3个字符串为0) 索引3: 1次 (仅第3个字符串第3位是1) 索引4: 1次 (仅第1个字符串第4位是1) ``` 3. 输出:`min(3,2,2,1,1) = 1`(因统计了5个索引位置)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值