news 2026/4/3 4:49:01

算法题 设计哈希集合

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算法题 设计哈希集合

设计哈希集合

问题描述

不使用任何内建的哈希表库设计一个哈希集合(HashSet)。

实现MyHashSet类:

  • void add(key)向哈希集合中插入一个值key
  • bool contains(key)返回哈希集合中是否包含这个值key
  • void remove(key)将给定值key从哈希集合中删除。如果哈希集合中没有该值,什么也不做。

约束条件

  • 0 <= key <= 10^6
  • 最多调用10^4addremovecontains方法

示例

输入: ["MyHashSet", "add", "add", "contains", "contains", "add", "contains", "remove", "contains"] [[], [1], [2], [1], [3], [2], [2], [2], [2]] 输出: [null, null, null, true, false, null, true, null, false] 解释: MyHashSet myHashSet = new MyHashSet(); myHashSet.add(1); // set = {1} myHashSet.add(2); // set = {1, 2} myHashSet.contains(1); // 返回 True myHashSet.contains(3); // 返回 False ,(未找到) myHashSet.add(2); // set = {1, 2} myHashSet.contains(2); // 返回 True myHashSet.remove(2); // set = {1} myHashSet.contains(2); // 返回 False ,(已被删除)

算法思路

方法一:布尔数组

  • 核心思想:直接使用长度为10^6+1的布尔数组

方法二:链地址

  • 核心思想
    • 使用桶数组存储链表
    • 哈希函数:key % bucketSize
    • 冲突处理:链表存储冲突的键

方法三:开放地址

  • 核心思想:冲突时寻找下一个空槽位

方法四:位图(BitSet)

  • 核心思想:使用位数组,每个位表示一个键的存在性

代码实现

方法一:布尔数组

classMyHashSet{privateboolean[]set;privatestaticfinalintMAX_KEY=1000000;/** * 初始化哈希集合 * 使用布尔数组直接映射 */publicMyHashSet(){set=newboolean[MAX_KEY+1];}/** * 向哈希集合中添加键 * * @param key 要添加的键 (0 <= key <= 10^6) */publicvoidadd(intkey){set[key]=true;}/** * 从哈希集合中删除键 * * @param key 要删除的键 */publicvoidremove(intkey){set[key]=false;}/** * 检查哈希集合是否包含指定键 * * @param key 要检查的键 * @return 如果包含返回true,否则返回false */publicbooleancontains(intkey){returnset[key];}}

方法二:链地址

importjava.util.LinkedList;importjava.util.List;classMyHashSet{privatestaticfinalintBUCKET_SIZE=1000;privateList<Integer>[]buckets;/** * 初始化哈希集合 * 使用链地址处理冲突 */@SuppressWarnings("unchecked")publicMyHashSet(){// 创建桶数组,每个桶是一个链表buckets=newLinkedList[BUCKET_SIZE];for(inti=0;i<BUCKET_SIZE;i++){buckets[i]=newLinkedList<>();}}/** * 哈希函数:计算键对应的桶索引 */privateinthash(intkey){returnkey%BUCKET_SIZE;}/** * 向哈希集合中添加键 */publicvoidadd(intkey){intbucketIndex=hash(key);List<Integer>bucket=buckets[bucketIndex];// 避免重复添加if(!bucket.contains(key)){bucket.add(key);}}/** * 从哈希集合中删除键 */publicvoidremove(intkey){intbucketIndex=hash(key);List<Integer>bucket=buckets[bucketIndex];// 从桶中删除键bucket.remove(Integer.valueOf(key));}/** * 检查哈希集合是否包含指定键 */publicbooleancontains(intkey){intbucketIndex=hash(key);List<Integer>bucket=buckets[bucketIndex];returnbucket.contains(key);}}

方法三:优化链地址

classMyHashSet{privatestaticfinalintBUCKET_SIZE=1000;privateNode[]buckets;// 链表节点定义privatestaticclassNode{intkey;Nodenext;Node(intkey){this.key=key;}}publicMyHashSet(){buckets=newNode[BUCKET_SIZE];}privateinthash(intkey){returnkey%BUCKET_SIZE;}publicvoidadd(intkey){intbucketIndex=hash(key);Nodehead=buckets[bucketIndex];// 检查是否已存在Nodecurrent=head;while(current!=null){if(current.key==key){return;// 已存在,无需添加}current=current.next;}// 在头部插入新节点NodenewNode=newNode(key);newNode.next=head;buckets[bucketIndex]=newNode;}publicvoidremove(intkey){intbucketIndex=hash(key);Nodehead=buckets[bucketIndex];if(head==null){return;}// 如果要删除的是头节点if(head.key==key){buckets[bucketIndex]=head.next;return;}// 在链表中查找并删除Nodecurrent=head;while(current.next!=null){if(current.next.key==key){current.next=current.next.next;return;}current=current.next;}}publicbooleancontains(intkey){intbucketIndex=hash(key);Nodecurrent=buckets[bucketIndex];while(current!=null){if(current.key==key){returntrue;}current=current.next;}returnfalse;}}

方法四:位图

classMyHashSet{privatestaticfinalintMAX_KEY=1000000;privatestaticfinalintINT_BITS=32;privateint[]bitSet;publicMyHashSet(){// 计算需要的整数数组大小intsize=(MAX_KEY+INT_BITS)/INT_BITS;bitSet=newint[size];}privateintgetArrayIndex(intkey){returnkey/INT_BITS;}privateintgetBitIndex(intkey){returnkey%INT_BITS;}publicvoidadd(intkey){intarrayIndex=getArrayIndex(key);intbitIndex=getBitIndex(key);// 设置对应位为1bitSet[arrayIndex]|=(1<<bitIndex);}publicvoidremove(intkey){intarrayIndex=getArrayIndex(key);intbitIndex=getBitIndex(key);// 设置对应位为0bitSet[arrayIndex]&=~(1<<bitIndex);}publicbooleancontains(intkey){intarrayIndex=getArrayIndex(key);intbitIndex=getBitIndex(key);// 检查对应位是否为1return(bitSet[arrayIndex]&(1<<bitIndex))!=0;}}

算法分析

  • 时间复杂度
    • 布尔数组/位图:所有操作O(1)
    • 链地址:平均O(1),最坏O(n)(所有键哈希到同一桶)
  • 空间复杂度
    • 布尔数组:O(10^6)
    • 位图:O(10^6/32)
    • 链地址:O(n),n为实际存储的键数量

测试用例

publicclassTestMyHashSet{publicstaticvoidmain(String[]args){// 测试用例1:标准示例System.out.println("Test 1: ");MyHashSetmyHashSet1=newMyHashSet();myHashSet1.add(1);myHashSet1.add(2);System.out.println("contains(1): "+myHashSet1.contains(1));// trueSystem.out.println("contains(3): "+myHashSet1.contains(3));// falsemyHashSet1.add(2);// 重复添加System.out.println("contains(2): "+myHashSet1.contains(2));// truemyHashSet1.remove(2);System.out.println("contains(2): "+myHashSet1.contains(2));// falseSystem.out.println();// 测试用例2:边界值System.out.println("Test 2: ");MyHashSetmyHashSet2=newMyHashSet();myHashSet2.add(0);myHashSet2.add(1000000);System.out.println("contains(0): "+myHashSet2.contains(0));// trueSystem.out.println("contains(1000000): "+myHashSet2.contains(1000000));// truemyHashSet2.remove(0);myHashSet2.remove(1000000);System.out.println("contains(0): "+myHashSet2.contains(0));// falseSystem.out.println("contains(1000000): "+myHashSet2.contains(1000000));// falseSystem.out.println();// 测试用例3:重复操作System.out.println("Test 3: ");MyHashSetmyHashSet3=newMyHashSet();myHashSet3.add(5);myHashSet3.add(5);myHashSet3.add(5);System.out.println("contains(5): "+myHashSet3.contains(5));// truemyHashSet3.remove(5);myHashSet3.remove(5);// 重复删除myHashSet3.remove(5);System.out.println("contains(5): "+myHashSet3.contains(5));// falseSystem.out.println();// 测试用例4:大量操作System.out.println("Test 4:");MyHashSetmyHashSet4=newMyHashSet();for(inti=0;i<1000;i++){myHashSet4.add(i*1000);// 添加0, 1000, 2000, ..., 999000}// 验证添加的元素booleanallPresent=true;for(inti=0;i<1000;i++){if(!myHashSet4.contains(i*1000)){allPresent=false;break;}}// 删除一半元素for(inti=0;i<500;i++){myHashSet4.remove(i*1000);}// 验证删除的元素不存在,未删除的元素存在booleandeletionCorrect=true;for(inti=0;i<500;i++){if(myHashSet4.contains(i*1000)){deletionCorrect=false;break;}}for(inti=500;i<1000;i++){if(!myHashSet4.contains(i*1000)){deletionCorrect=false;break;}}System.out.println();// 测试用例5:空集合操作System.out.println("Test 5: ");MyHashSetmyHashSet5=newMyHashSet();System.out.println("contains(42): "+myHashSet5.contains(42));// falsemyHashSet5.remove(42);// 删除不存在的元素System.out.println("contains(42): "+myHashSet5.contains(42));// falsemyHashSet5.add(42);System.out.println("contains(42): "+myHashSet5.contains(42));// true}}

关键点

  1. 哈希函数

    • 简单取模:key % bucketSize
    • 桶数量选择:通常选择质数或2的幂次
  2. 冲突处理

    • 链地址:每个桶维护一个链表
    • 开放地址:寻找下一个空槽
    • 再哈希:使用第二个哈希函数
  3. 去重处理

    • 添加前检查是否已存在
    • 避免集合中出现重复元素
  4. 删除操作

    • 链表删除需要处理头节点和中间节点
    • 布尔数组和位图直接设置对应位置

常见问题

  1. 为什么链地址的桶数量选择1000?

    • 桶数量太小会导致链表过长,影响性能
    • 桶数量太大会浪费空间
  2. 布尔数组会超内存?

    • 10^6个布尔值约1MB内存,可以接受
  3. 如何处理负数键?

    • key≥0,不需要处理负数
    • 如果需要处理负数,哈希函数需要调整:(key % bucketSize + bucketSize) % bucketSize
  4. 链地址的最坏情况?

    • 所有键都哈希到同一个桶,退化为链表
    • 时间复杂度变为O(n),概率很低
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 17:09:27

JeeLowCode终极指南:快速掌握企业级低代码开发框架

JeeLowCode终极指南&#xff1a;快速掌握企业级低代码开发框架 【免费下载链接】jeelowcode &#x1f525;JeeLowCode 【企业级低代码】 是一款专为企业打造的低代码开发框架《免费商用》&#xff0c;以低代码为核心&#xff0c;实现快速开发。提供可视化界面&#xff0c;拖拽组…

作者头像 李华
网站建设 2026/3/24 1:42:24

Lawyer LLaMA 完整指南:构建专业法律AI助手的终极教程

Lawyer LLaMA 完整指南&#xff1a;构建专业法律AI助手的终极教程 【免费下载链接】lawyer-llama 中文法律LLaMA (LLaMA for Chinese legel domain) 项目地址: https://gitcode.com/gh_mirrors/la/lawyer-llama 在人工智能技术快速发展的今天&#xff0c;专业领域的AI应…

作者头像 李华
网站建设 2026/3/29 15:09:42

CCF程序员大会 | 全球协作:数字游民新生态

当“数字游民”从一个时髦词汇变成越来越多程序员向往的生活方式&#xff0c;你是否也曾幻想&#xff1a;告别固定工位&#xff0c;只需一台电脑&#xff0c;就能在山海间实现工作与生活的平衡&#xff1f;真正的数字游民生活&#xff0c;远不止“诗和远方”。如何合法合规地承…

作者头像 李华
网站建设 2026/3/30 6:08:25

微服务架构下工具发现难题,Docker MCP 网关如何实现秒级响应?

第一章&#xff1a;微服务架构下工具发现的挑战与演进 在微服务架构广泛应用的今天&#xff0c;服务实例动态性强、分布广泛&#xff0c;使得服务发现成为系统稳定运行的关键环节。传统的静态配置方式已无法满足快速扩缩容和故障迁移的需求&#xff0c;服务发现机制必须具备实时…

作者头像 李华
网站建设 2026/4/3 4:42:36

Wan2.2-T2V-A14B在节日营销视频批量生成中的实战案例

Wan2.2-T2V-A14B在节日营销视频批量生成中的实战案例 你有没有经历过这种场景&#xff1f; 双11前一周&#xff0c;市场部突然说&#xff1a;“我们要给全国30个城市做本地化广告视频&#xff01;” 原本以为要拍一个月的片子&#xff0c;结果……AI十分钟全搞定了 ✨ 这听起…

作者头像 李华