哈希表实现[很详细!]

目录

哈希表

定义节点类

根据hash码获取value

向hash表存入新key value,如果key重复,则更新value

根据hash码删除,返回删除的value

关于resize()一些问题的解答

冲突测试

MurmurHash

设计思考

练习

Leetcode01

Leetcode03

Leetcode49

Leetcode217

Leetcode136

Leetcode242

Leetcode387

Leetcode819


哈希表

查找数据红黑树B树是对数时间,但是哈希表的速度更快O(1),增删查改

定义节点类

static class Entry{
        int hash;//哈希码
        Object key;//键
        Object value;//值
        Entry next;

        public Entry(int hash, Object key, Object value) {
            this.hash = hash;
            this.key = key;
            this.value = value;
        }
    }
    
    //数组存链表头指针即可
    Entry[] table = new Entry[16];//选择2^n
    int size=0;//元素个数

根据hash码获取value

 /*求模运算替换为位运算
        - 前提:数组长度是2的n次方
        - hash % 数组长度 等价于 hash & (数组长度-1)
     */

    // 根据hash码获取value
    Object get(int hash,Object key){
        int idx = hash & (table.length-1);
        if(table[idx] == null){
            return null;
        }
        Entry p = table[idx];
        while(p!=null){
            if(p.key.equals(key)){
                return p.value;
            }
            p=p.next;
        }
        return null;
    }

向hash表存入新key value,如果key重复,则更新value

// 向hash表存入新key value,如果key重复,则更新value
    void put(int hash,Object key,Object value){
        int idx = hash & (table.length-1);
        if(table[idx] == null){
            table[idx] = new Entry(hash,key,value);

        }else{
            Entry p = table[idx];
            while(true){
                if(p.key.equals(key)){
                    p.value = value;//更新
                    return;
                }
                if(p.next==null){
                    break;
                }
                p=p.next;
            }
            p.next = new Entry(hash,key,value);//新增
        }
        size++;
    }

根据hash码删除,返回删除的value

 // 根据hash码删除,返回删除的value
    Object remove(int hash,Object key){
        int idx = hash&(table.length-1);
        if(table[idx]==null){
            return null;
        }
        Entry p = table[idx];
        Entry prev = null;
        while(p!=null){
            if(p.key.equals(key)){
                //找到了删除 -- 参考单向链表删除
                if(prev==null){//删的是链表头元素
                    table[idx]=p.next;
                }else{
                    prev.next = p.next;
                }
                size--;
                return p.value;
            }
            prev=p;
            p=p.next;
        }
        return null;
    }

什么时候对哈希表扩容合适呢?

这里引入一个名词:负载因子 load factor(a) = n/m,  [n:元素个数 m:数组长度]

这个load factor不宜过大也不宜太小,在Java中合适取值为3/4,这是一个经验值.

扩容后我们需要将数据转移到新数组中,要重新计算索引

   float loadFactor = 0.75f;// 0.75 * 16 = 12(阈值)超过就扩容
    int threshold = (int)(loadFactor*table.length);
    /*求模运算替换为位运算
        - 前提:数组长度是2的n次方
        - hash % 数组长度 等价于 hash & (数组长度-1)
     */

    // 根据hash码获取value
    Object get(int hash,Object key){
        int idx = hash & (table.length-1);
        if(table[idx] == null){
            return null;
        }
        Entry p = table[idx];
        while(p!=null){
            if(p.key.equals(key)){
                return p.value;
            }
            p=p.next;
        }
        return null;
    }

    // 向hash表存入新key value,如果key重复,则更新value
    void put(int hash,Object key,Object value){
        int idx = hash & (table.length-1);
        if(table[idx] == null){
            table[idx] = new Entry(hash,key,value);

        }else{
            Entry p = table[idx];
            while(true){
                if(p.key.equals(key)){
                    p.value = value;//更新
                    return;
                }
                if(p.next==null){
                    break;
                }
                p=p.next;
            }
            p.next = new Entry(hash,key,value);//新增
        }
        size++;
        if(size>threshold){
            resize();
        }
    }

    private void resize(){
        Entry[] newTable = new Entry[table.length<<1];
        for(int i = 0;i<table.length;i++){
            Entry p = table[i];//拿到每个链表头
            if(p!=null){
                //拆分链表,移动到新数组
                /*
                    拆分规律
                    * 一个链表最多拆成两个
                    * hash & table.length == 0 的一组
                    * hash & table.length != 0 的一组
                                               p
                    0->8->16->24->32->40->48->null

                                a
                    0->16->32->48->null

                           b
                    8->24->40->null
                 */
                Entry a = null;
                Entry b = null;
                Entry aHead = null;
                Entry bHead = null;
                while(p!=null){
                    if((p.hash & table.length)==0){
                        if(a!=null){
                            a.next = p;
                        }else{
                            aHead =p;
                        }
                        //分配到a
                        a = p;
                    }else{
                        if(b!=null){
                            b.next = p;
                        }else{
                            bHead = p;
                        }
                        //分配到b
                        b = p;
                    }
                    p=p.next;
                }
                // 规律: a 链表保持索引位置不变, b 链表索引位置+table.length
                if(a!=null){
                    a.next = null;
                    newTable[i] = aHead;
                }
                if(b!=null){
                    b.next = null;
                    newTable[i+table.length] = bHead;
                }
            }

        }
        table = newTable;
        threshold=(int)(loadFactor*table.length);
    }

关于resize()一些问题的解答

/*
        为什么计算索引位置用式子:hash & (数组长度-1)  等价于 hash % 数组长度
        解释: %10看后一位   %100看后两位
            30%2==0
            0011110 % 0000010 = 0000000 看后1位
       4    0011110 % 0000100 = 0000010 看后2位
            0011110 % 0001000 = 0000110 ...
            0011110 % 0010000 = 0001110 ...
            0011110 % 0100000 = 0011110  ...
         文字表达: 10进制中去除以 10,100,1000时,余数就是被除数的后1,2,3位
                    10^1  10^2  10^3
                  2进制中去除以10,100,1000时,余数也是被除数的后1,2,3位
                        2^1  2^2   2^3
                  :任何一个数字跟 0按位与都等于0 跟1按位与能保留该位



        为什么旧链表会拆分成两条,一条hash & 旧数组长度==0 另一条!=0
        解释:
        旧数组长度换算成二进制后,其中的1就是我们要检查的倒数第几位
              旧数组长度 8 二进制 => 1000 检查倒数第4位
              旧数组长度 16 二进制 => 10000 检查倒数第5位
            hash & 旧数组长度,就是用来检查扩容前后索引位置(余数) 会不会变



        为什么拆分后的两条链表,一条原索引不变,另一个是原索引+旧数组长度
        解释:跟上面那个问题一样  二进制第i位 只能是0或者1


        它们都有个共同的前提:数组长度是2的n次方

     */

public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Object obj = new Object();
            System.out.println(obj.hashCode());
/*
1922154895
883049899
2093176254
1854731462
317574433
885284298
1389133897
1534030866
664223387
824909230
*/

            }
        Object obj1 = new Object();
        for (int i = 0; i < 10; i++) {
            System.out.println(obj1.hashCode());//同一对象的hashCode值是一样的
        }
    }
  public Object get(Object key){
        int hash = getHash(key);
        return get(hash,key);
    }

    public void put(Object key,Object value){
        int hash = getHash(key);
        put(hash,key,value);
    }

    public Object remove(Object key){
        int hash = getHash(key);
        return remove(hash,key);
    }

    private static int getHash(Object key) {
        int hash = key.hashCode();
        return hash;
    }

但是更多时候我们是不想用Object父类给我们提供的hashCode方法的,比如字符串类

  public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");

        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());//结果发现两个哈希码一样 说明父类的hashcode被重写了
        //那我们来看看字符串的哈希码如何生成的

        //原则:值相同的字符串生成相同的hash码,尽量让值不同的字符串生成不同的hash码
        /*
        对于 abc a*100 + b*10 + c
        对应 bac b*100 + a*10 + c
         */
        int hash=0;
        for (int i = 0; i < s1.length(); i++) {
            char c = s1.charAt(i);
            System.out.println((int)c);
//            hash= hash*10 + c;//这个10 换成31就是底层内部的实现了
//            hash = hash*31+c;
            hash = (hash<<5)-hash+c; //这样效率比乘法更高
        }
        System.out.println(hash);
    }

冲突测试

检测链表长度

 public void print(){
        int[] sums = new int[table.length];
        for(int i = 0;i<table.length;i++){
            Entry p = table[i];
            while(p!=null){
                sums[i]++;
                p = p.next;
            }
        }
        System.out.println(Arrays.toString(sums));
    }

    public static void main(String[] args) {
        HashTable table = new HashTable();
        for (int i = 0; i < 20; i++) {
            Object obj = new Object();
            table.put(obj,obj);
        }
        table.print();
    }

当然数量可能还是有点少

    public void print(){
        int[] sums = new int[table.length];
        for(int i = 0;i<table.length;i++){
            Entry p = table[i];
            while(p!=null){
                sums[i]++;
                p = p.next;
            }
        }
//        System.out.println(Arrays.toString(sums));

        Map<Integer, Long> collect = Arrays.stream(sums).boxed().
                collect(Collectors.groupingBy(e -> e, Collectors.counting()));
        System.out.println(collect);
    }

    public static void main(String[] args) {
        HashTable table = new HashTable();
        for (int i = 0; i < 200000; i++) {
            Object obj = new Object();
            table.put(obj,obj);
        }
        table.print();
    }

如果对字符串呢?

    public void print(){
        int[] sums = new int[table.length];
        for(int i = 0;i<table.length;i++){
            Entry p = table[i];
            while(p!=null){
                sums[i]++;
                p = p.next;
            }
        }
//        System.out.println(Arrays.toString(sums));

        Map<Integer, Long> collect = Arrays.stream(sums).boxed().
                collect(Collectors.groupingBy(e -> e, Collectors.counting()));
        System.out.println(collect);
    }

    public static void main(String[] args) throws IOException {
        HashTable table = new HashTable();
        List<String> strings = Files.readAllLines(Path.of("C:\\大一学习\\Java\\YJY\\learning\\src\\Tree\\a.txt"));
        for (String string : strings) {
            table.put(string,string);
        }
        table.print();
    }
}

MurmurHash

第二代哈希算法

  • 引入google guava 依赖
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

对比:

设计思考

1.Java内部HashTable 就是运用头插法 HashMap 在1.7版本也是头插法,后来因为在多线程下会导致死循环问题所以改为尾插法

2.

在我们自己的实现中:

源代码中通过 hash^(hash>>>16) 这样就能够避免上面产生的问题

不过这是有前提的 是要在数组容量是2^n次方下,如果不是就可以不用

3.   ==> 2^n   按位与   拆分链表   高低位异或

Java中的HashTable 就不是用2^n次方 初始容量是11 是一个质数 求模算余数分布性更好 因此也不需要高低位异或 

4.防患于未然  有的人造攻击的哈希数组,一旦造成冲突会导致服务器性能下降,这种做法是一种保底的做法 一般都是6到头了  除非恶意

练习

Leetcode01

1. 两数之和 - 力扣(LeetCode)

import java.util.HashMap;

/**
 * <h3>两数之和</h3>
 * 给定一个整数数组nums和一个整数目标值target,请你在该数组中找出和为目标值target的那两个整数,并返回他们的数组下标
 * 注意:<strong>只会存在一个有效答案</strong>
 */
public class Leetcode01 {
    /*
        [(2,0),(6,1),]


        输入: nums = [2,7,11,15],target = 9
        输出: [0,1]
        解释: 因为 nums[0] + nums[1] == 9,返回[0,1]

        输入: nums = [3,3],target = 6
        输出: [1,2]

        输入: nums = [3,3],target = 6
        输出: [0,1]

        思路:
        1.循环遍历数组,拿到每个数字 X
        2.以 target-x作为key到hash表查找
            1) 若没找到,将x作为key,它的索引作为value放入hash表
            2) 若找到了,返回x和它配对数的索引即可
     */

    public int[] twoSum(int[] nums,int target){
        HashMap<Integer,Integer>map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int x = nums[i];
            int y = target-x;
            if(map.containsKey(y)){
                return new int[]{i,map.get(y)};
            }else{
                map.put(x,i);
            }
        }
        return null;
    }
}
Leetcode03

3. 无重复字符的最长子串 - 力扣(LeetCode)

import java.util.HashMap;

/**
 * <h2>无重复字符的最长子串</h2>
 * <p>1.给定一个字符串s,请你找出其中不含有重复字符的最长子串的长度.</p>
 * <p>2.s由英文字母,数字,符号和空格组成</p>
 */
public class Leetcode03 {

    public int lengthOfLongestSubstring(String s){
        HashMap<Character,Integer> map = new HashMap<>();//key返回比较大用hashMap 但是这道题目仅仅是128个字符可以用数组
        int begin = 0;
        int maxLength = 0;
        for(int end = 0;end<s.length();end++){
            char ch = s.charAt(end);
            if (map.containsKey(ch)) {//重复时,调整begin
                begin = Math.max(begin,map.get(ch) + 1);//防止begin回退
                map.put(ch,end);
            }else{//没有重复
                map.put(ch,end);
            }
//            System.out.println(s.substring(begin,end+1));
            maxLength = Math.max(maxLength,end - begin +1);
        }
        return maxLength;
    }

    public static void main(String[] args){
//        System.out.println(new Leetcode03().lengthOfLongestSubstring("abcabcbb"));
        Leetcode03 e02 = new Leetcode03();
//        System.out.println(e02.lengthOfLongestSubstring("abcabcbb"));
        System.out.println(e02.lengthOfLongestSubstring("abba"));
        /*
            [(a,0),(b,2)]
             b
               e
            abba
         */



         /*
            a
            ab
            abc
            bca
            cab
            abc
            cb
            b
          */

        /*
            给定一个字符串 s ,请你找出其中不含重复字符的最长子串的长度
            abcabcbb   3
            a
            ab
            abc
            bca
            cab
            abc
            cb
            b

            bbbbbb      1
            b

            pwwkew      3
            p
            pw
            w
            wk
            wke
            kew

            [(a,3),(b,7),(c,5)]
                 b
                   e
            abcabcbb

        要点:
            1.用 begin 和 end 表示子串开始和结束位置
            2.用hash表检查重复字符
            3.从左到右查看每一个字符,如果:
                - 没遇到重复字符,调整end
                - 遇到重复的字符,调整begin
                - 将当前字符放入hash表
            4.end - begin + 1 是当前子串长度
         */

    }
}

运用了滑动窗口的思路

滑动窗口做题思路-CSDN博客

Leetcode49

49. 字母异位词分组 - 力扣(LeetCode)

import java.util.*;

/**
 * 字母异位次分组
 */
public class Leetcode49 {

    /*
        思路:
        1.遍历字符串数组,每个字符串中的字符重新排序后作为key
        2.所谓分组,其实就是准备一个集合,把这些单词加入到key相同的集合中
        3.返回分组结果
     */
    public List<List<String>> groupAnagrams(String[] strs){  // 6ms
        HashMap<String, List<String>>map = new HashMap<>();
        for (String str : strs) {
            char[] chars = str.toCharArray();
            Arrays.sort(chars);
            String key = new String(chars);//字符数组->字符串
            List<String> list = map.computeIfAbsent(key, k -> new ArrayList<>());
            //computeIfAbsent原理:先看key在map中是否存在,如果缺失(absent)就会创建一个新的集合放到集合中
            //如果不是absent那就不会创建  然后把list集合作为结果进行返回
            list.add(str);
        }
        return new ArrayList<>(map.values());//map.values就是分的组
    }

    public static void main(String[] args) {
        // ab  ba
        // eat eta tae tea ate aet
        String[] strs = {"eat","tea","tan","ate","nat","bat"};
        // eat tea ate => aet
        // tan nat     => ant
        // bat         => abt
        // [(aet,{eat,tea}), (ant,{tan})]
        List<List<String>>lists = new Leetcode49().groupAnagrams(strs);
        System.out.println(lists);
    }
}

computeIfAbsent()那一段可以这么写: 性能上是差不多的只是看起来更简洁

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        HashMap<String,List<String>>map = new HashMap<>();
        for(String str:strs){
            char[] chars = str.toCharArray();
            Arrays.sort(chars);
            String key = new String(chars);
            List<String>list = map.get(key);
            if(list ==null){
                list = new ArrayList<>();
                map.put(key,list);
            }
            list.add(str);
        }
        return new ArrayList<>(map.values());
    }
}

第二种方法:效率更高 5ms

  static class ArrayKey {//一个整数数组做一个封装
        int[] key = new int[26];
        

        //alt+insert ==>生成 基于数组的equals 和 hashCode
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;//同一对象
            if (o == null || getClass() != o.getClass()) return false;
            ArrayKey arrayKey = (ArrayKey) o;
            return Arrays.equals(key, arrayKey.key);//比较内容
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(key);
        }
        //将字符串变成整数数组
        public ArrayKey(String str){
            for (int i = 0; i < str.length(); i++) {
                char ch = str.charAt(i);// 'a'97-97= 0(映射到索引0) 'b'98-97=1  'a'
                key[ch-97]++;
            }
        }
    }

    /*
    bitmap 位图思想
        题目有说明:strs[i] 仅包含小写字母
        key = [2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0]  26
        key = [2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0]  26
        "eaat","teaa"
     */
    public List<List<String>> groupAnagrams(String[] strs){
        HashMap<ArrayKey,List<String>>map = new HashMap<>();
        for (String str : strs) {
            ArrayKey key = new ArrayKey(str);
            List<String> list = map.computeIfAbsent(key, k -> new ArrayList<>());
            list.add(str);
        }
        return new ArrayList<>(map.values());
    }

Leetcode217

217. 存在重复元素 - 力扣(LeetCode)

    /*
    [1,2,3,1]

    [1,2,3] => true
     */

    public boolean containsDuplicate1(int[] nums) {//11ms
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int key:nums){
            if(map.containsKey(key)){
                //找到重复
                return true;
            }
            map.put(key,key);//一次循环调用put 和 containsKey
        }
        return false;
    }
    public boolean containsDuplicate(int[] nums){//5ms
        HashSet<Integer>set = new HashSet<>();
        for(int key:nums){
            if(!set.add(key)){//一次循环调用一次put
                return true;
            }
        }
        return false;
    }

通过分析我们可以来这样修改我们的代码:6ms

  public boolean containsDuplicate(int[] nums) {
        HashMap<Integer,Object>map = new HashMap<>(nums.length*2);
        Object value = new Object();
        for(int key:nums){
            Object put = map.put(key,value);
            if(put!=null){
                return true;
            }
        }
        return false;
    }
Leetcode136

136. 只出现一次的数字 - 力扣(LeetCode)

import java.util.HashSet;

/**
 * 找出出现一次的数字
 * 除了某个元素只出现一次以外,其余每个元素均出现两次
 */
public class Leetcode136 {
    /*

        输入:nums = [2,2,1]
        输出:1
        HashSet
        [2,] 遇到重复的就把第一个拿出来 最后集合中唯一的元素就是只出现一次的答案

        输入:nums = [4,1,2,1,2]
        输出4
        [4]
思路一:
        1.准备一个Set 集合,逐一放入数组元素
        2.遇到重复的,则删除
        3.最后留下来的,就是那个没有重复的数字
     */
    public int singleNumber1(int[] nums){//效率低
        HashSet<Integer> set = new HashSet<>();
        for(int num:nums){
            if (!set.add(num)) {
                set.remove(num);
            }
        }
        return set.toArray(new Integer[0])[0];
    }
    /*
    思路二:
    1.任何相同的数字异或,结果都是0
    2.任何数字与0异或,结果都是数字本身
     */

    public int singleNumber(int[] nums){
        int num = nums[0];
        for(int i=1;i<nums.length;i++){
            num = num^nums[i];
        }
        return num;
    }

    public static void main(String[] args) {
        Leetcode136 e06 = new Leetcode136();
        System.out.println(e06.singleNumber(new int[]{2, 2, 1}));
        System.out.println(e06.singleNumber(new int[]{4, 1, 2, 1, 2}));
    }
}

Leetcode242

242. 有效的字母异位词 - 力扣(LeetCode)

import java.util.Arrays;

public class Leetcode242 {

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

          1.拿到字符数组,排序后比较字符数组
          2.字符打散放入 int[26] 比较数组
    */
    public boolean isAnagram(String s,String t){
        return Arrays.equals(getKey(s),getKey(t));
    }
    private int[] getKey1(String s){//3ms
        int[] array = new int[26];
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i); //'a' - 97 = 0
            array[ch-97]++;
        }
        return array;
    }

    private int[] getKey(String s){//1ms
        int[] array = new int[26];
        char[] chars = s.toCharArray();
        for(char ch:chars){
            array[ch-97]++;
        }
        return array;
    }
}
Leetcode387

387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

public class Leetcode387 {
    public int firstUniqChar(String s) {
        int[] array = new int[26];
        char[] chars = s.toCharArray();
        for (char ch : chars) {
            array[ch - 97]++;
        }
        for (int i = 0; i < chars.length; i++) {
            char ch = chars[i];
            if(array[ch-97]==1){
                return i;
            }
        }
        return -1;
    }
}
Leetcode819

819. 最常见的单词 - 力扣(LeetCode)

这种方法14ms 非常不理想

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public class Leetcode819 {
    /*
        1. 将Paragraph截取为一个个单词
        2.将单词加入map集合,单词本身作为key,出现次数作为value,避免禁用词加入
        3.在map集合中找到value最大的,返回它对应的key即可
     */
    public String mostCommonWord(String paragraph,String[] banned){
        //1
        String[] split = paragraph.toLowerCase().split("[^A-Za-z]+");
        Set<String> set = Set.of(banned);//Java9以上版本 转成set集合
        HashMap<String,Integer>map = new HashMap<>();
        for (String key : split) {
            if(!set.contains(key)){//如果set中包含了key 就不加入 不包含再加入
                map.compute(key,(k,v)->v==null?1:v+1);
            }
//            System.out.println(key);

//            Integer value = map.get(key);
//            if(value==null){
//                value=0;
//            }
//            map.put(key,value+1);

//            map.compute(key,(k,v)->v==null?1:v+1);//第一次放入1 不是第一次就加1 跟上面4行等效
        }
//        System.out.println(map);
        Optional<Map.Entry<String, Integer>> max = map.entrySet().stream().max(Map.Entry.comparingByValue());
        System.out.println(max);
        return max.map(Map.Entry::getKey).orElse(null);
    }
}

改进1: 

  public String mostCommonWord(String paragraph,String[] banned){//12ms
        //1
        String[] split = paragraph.toLowerCase().split("[^A-Za-z]+");
        Set<String> set = Set.of(banned);//Java9以上版本 转成set集合
        HashMap<String,Integer>map = new HashMap<>();
        for (String key : split) {
            if(!set.contains(key)){//如果set中包含了key 就不加入 不包含再加入
                map.compute(key,(k,v)->v==null?1:v+1);
            }
        }
        int max = 0;
        String maxKey = null;
        for (Map.Entry<String, Integer> e : map.entrySet()) {
            Integer value = e.getValue();
            if(value>max){
                max= value;
                maxKey = e.getKey();
            }
        }
        return maxKey;
    }
import com.sun.jdi.event.StepEvent;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public class Leetcode819 {
    /*
        1. 将Paragraph截取为一个个单词
        2.将单词加入map集合,单词本身作为key,出现次数作为value,避免禁用词加入
        3.在map集合中找到value最大的,返回它对应的key即可
     */
    public String mostCommonWord(String paragraph,String[] banned){// 4ms
//        String[] split = paragraph.toLowerCase().split("[^A-Za-z]+");
        Set<String>set = Set.of(banned);
        HashMap<String,Integer>map = new HashMap<>();
        char[] chars = paragraph.toLowerCase().toCharArray();
        StringBuilder sb =new StringBuilder();
        for (char ch : chars) {
            if(ch>='a'&&ch<='z'){
                sb.append(ch);
            }else{
                String key = sb.toString();
                if(!set.contains(key)){
                    map.compute(key,(k,v)->v==null?1:v+1);
                }
//                sb = new StringBuilder();
                sb.setLength(0);//不创建置为0
            }
        }
        if(sb.length()>0){
            String key = sb.toString();
            if(!set.contains(key)){
                map.compute(key,(k,v)->v==null?1:v+1);
            }
        }
        System.out.println(map);

        int max = 0;
        String maxKey = null;
        for (Map.Entry<String, Integer> e : map.entrySet()) {
            Integer value = e.getValue();
            if(value>max){
                max= value;
                maxKey = e.getKey();
            }
        }
        return maxKey;
    }
}

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
哈希表(Hash Table),也称为散列表,是一种基于键值对的数据结构。它通过使用哈希函数将键映射到数组的特定位置来实现快速的插入、删除和查找操作。 哈希表由一个数组和一个哈希函数组成。数组的长度通常是固定的,根据实际需求预先分配。哈希函数将键转换为数组索引,使得每个键值对都可以在数组中找到对应的位置,这个位置被称为哈希桶(Hash Bucket)或槽(Slot)。 在哈希表中,元素的插入和查找过程如下: 1. 当需要插入一个键值对时,首先通过哈希函数计算出键的哈希码。哈希码是一个整数值,用于确定键在数组中的位置。 2. 根据哈希码找到数组中对应的位置。如果该位置没有被占用,则直接将键值对存储在该位置;如果该位置已经被占用,则发生了哈希冲突。 3. 处理哈希冲突。常见的处理方法有两种:开放地址法和链表法。 - 开放地址法:当发生哈希冲突时,通过一定的探测方式(如线性探测、二次探测等)去寻找下一个可用的位置来存储键值对。 - 链表法:在同一个哈希桶中,使用链表或者红黑树将具有相同哈希码的键值对连接起来。新的键值对可以插入链表的尾部或者红黑树中。 4. 在查找时,通过哈希函数计算出键的哈希码,然后根据哈希码定位到数组中的位置。如果该位置没有存储键值对,表示键不存在;如果该位置存储了键值对,则可以通过比较键的值来确定是否找到了目标元素。 哈希表的优点是具有快速的插入、删除和查找操作,平均情况下可以达到常数时间复杂度。然而,它也存在一些缺点: - 哈希冲突:不同的键可能会映射到相同的位置,需要通过哈希冲突的处理方法来解决。 - 空间占用:为了避免过多的哈希冲突,需要预先分配较大的数组空间,导致一定的空间浪费。 - 哈希函数选择:好的哈希函数能够均匀地分布键值对,减少哈希冲突的发生。 在实际应用中,哈希表常被用于需要高效查找的场景,如缓存、索引等。常见的哈希表实现包括Java中的HashMap、HashSet,C++中的unordered_map、unordered_set等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值