哈希表实现[很详细!]

news2024/9/28 11:23:30

目录

哈希表

定义节点类

根据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;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1615023.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络编程——TCP的特性之自动重传/流量控制/拥塞控制,一篇说清楚

文章目录 1. ARQ自动重传协议1.1 停止等待ARQ1.2 连续ARQ1.3 总结 2. TCP的流量控制3. TCP的拥塞控制3.1 慢开始算法3.2 拥塞避免算法3.3 快重传算法3.4 快恢复算法 1. ARQ自动重传协议 自动重传请求&#xff08;Automatic Repeat-reQuest&#xff09;&#xff0c;通过使用确认…

如何在Flask应用程序中使用JSON Web Tokens进行安全认证

密码、信用卡信息、个人识别号码&#xff08;PIN&#xff09;——这些都是用于授权和认证的关键资产。这意味着它们需要受到未经授权的用户的保护。 作为开发者&#xff0c;我们的任务是保护这些敏感信息&#xff0c;并且在我们的应用程序中实施强大的安全措施非常重要。 现在…

书生·浦语大模型实战营之 XTuner 微调 Llama 3 个人小助手认知

书生浦语大模型实战营之 XTuner 微调 Llama 3 个人小助手认知 Llama 3 近期重磅发布,发布了 8B 和 70B 参数量的模型,XTuner 团队对 Llama 3 微调进行了光速支持!!!开源同时社区中涌现了 Llama3-XTuner-CN 手把手教大家使用 XTuner 微调 Llama 3 模型。 XTuner:http://…

Pytorch 的数据处理 学习笔记

一. 数据集Dataset Dataset是一个抽象类&#xff0c;用于表示数据集。可以创建自定义的Dataset类来加载和准备好的数据&#xff0c;无论数据是存储在何处&#xff08;例如磁盘、内存或其他位置&#xff09;。PyTorch提供了一些内置的Dataset类&#xff0c;例如TensorDataset、…

【机器学习】特征筛选实例与代码详解

机器学习中的特征筛选 一、特征筛选的重要性与基本概念二、特征筛选的方法与实践1. 基于统计的特征筛选2. 基于模型的特征筛选3. 嵌入式特征筛选 三、总结与展望 在机器学习领域&#xff0c;特征筛选作为预处理步骤&#xff0c;对于提高模型性能、简化模型结构以及增强模型解释…

图像哈希:GLCM+DCT

文章信息 作者&#xff1a;Ziqing Huang期刊&#xff1a;IEEE&#xff08;一区&#xff09;题目&#xff1a;Perceptual Image Hashing with Texture and Invariant Vector Distance for Copy Detection 目的、实验步骤及结论 目的&#xff1a;使用GLCM进行全局特征的提取&am…

C# 开源SDK 工业相机库 调用海康相机 大恒相机

C# MG.CamCtrl 工业相机库 介绍一、使用案例二、使用介绍1、工厂模式创建实例2、枚举设备&#xff0c;初始化3、启动相机4、取图5、注销相机 三、接口1、相机操作2、启动方式3、取图4、设置/获取参数 介绍 c# 相机库&#xff0c;含海康、大恒品牌2D相机的常用功能。 底层采用回…

【Linux】在ubuntu快速搭建部署K8S(1.27)集群

ubuntu快速安装K8s1.27 &#xff08;一&#xff09;环境说明1.硬件环境2.Ubuntu环境设置 &#xff08;二&#xff09;安装配置containerd1.安装2.配置3.启动 &#xff08;三&#xff09;所有节点操作1.安装runc和cni2.节点系统设置、关闭临时分区3.修改内核参数4.安装 kubeadm、…

什么是 PCIe 及其工作原理?

什么是外围组件互连 Express (PCIe)&#xff1f; 外围组件互连 Express (PCIe) 是一种高速串行计算机扩展总线标准&#xff0c;可将设备连接到主板。 它于 2004 年首次推出&#xff0c;作为以前 PCI 和 AGP 方式的替代。 PCIe 允许处理器和各种扩展卡&#xff08;例如显卡、声…

PS入门|蒙版到底是个什么样的功能?看完就明白了

前言 前段时间一直说蒙版蒙版什么的&#xff0c;很多小伙伴估计都听得一头雾水。 抠个图要加蒙版&#xff0c;调个色要加蒙版。 小白感觉这个蒙版就像调味剂一样&#xff0c;啥都需要加一个蒙版。 动不动就加个蒙版&#xff0c;究竟是干啥用的&#xff1f; 今天咱们就深入来…

自动驾驶光学校准反射板

光学校准反射板是一种用于光学系统校准的重要工具。它以其高反射率和精确的几何特性&#xff0c;为光学仪器、光学系统和光学元件的校准提供了可靠的参考。在现代光学领域&#xff0c;光学校准反射板的应用已经深入到各个领域&#xff0c;从科学研究到工业生产&#xff0c;都离…

leetcode最大间距(桶排序+Python)

虽然直接排完序&#xff0c;再求最大差值更快&#xff0c;这里我们还是学一下桶排序。 桶排序主要维护一个bucket&#xff0c;初始bucket【i】 0&#xff0c;遍历nums&#xff0c;当i存在时&#xff0c;令bucket【i】 1&#xff0c;表示存在。遍历完nums&#xff0c;bucket中有…

海外平台运营为什么需要静态住宅IP?

在世界经济高度全球化的今天&#xff0c;许多企业家和电子商务卖家纷纷转向海外平台进行业务扩展。像亚马逊、eBay这样的跨国电商平台为卖家提供了巨大的机会&#xff0c;来接触到世界各地的顾客。然而&#xff0c;在这些平台上成功运营&#xff0c;尤其是维持账号的健康和安全…

代码随想录算法训练营第三十六天|435. 无重叠区间,763.划分字母区间,56. 合并区间

题目&#xff1a;435. 无重叠区间 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi]。返回需要移除区间的最小数量&#xff0c;使剩余区间互不重叠。 题目链接/讲解链接&#xff1a; https://programmercarl.com/0435.%E6%97%A0%E9%87%8D%E5%8F%A0…

C语言项目实践——贪吃蛇

引言&#xff1a;本篇博客中&#xff0c;我将会使用结构体&#xff0c;链表&#xff0c;WIN32 API等一系列知识完成C语言项目——贪吃蛇的实现。在观看此篇博客之前&#xff0c;请将这些知识所熟悉&#xff0c;不然可能会造成理解困难。 更多有关C语言的知识详解可前往个人主页…

AI预测福彩3D第9套算法实战化测试第1弹2024年4月22日第1次测试

经过前面多套算法的测试&#xff0c;总结了一些规律&#xff0c;对模型优化了一些参数&#xff0c;比如第8套算法的测试&#xff0c;7码的命中率由最开始的20%提高到了50%。虽然命中率有了很大的提高&#xff0c;但是由于咱们之前的算法只是为了测试和记录&#xff0c;提供的方…

C++学习进阶版(二):与文件相关的函数用法

目录 1、读取文件的指定行 &#xff08;1&#xff09;main函数中直接读 &#xff08;2&#xff09;封装成函数 ① 无返回值类型 ② 直接返回读取的内容 2、求文件的行数 3、文件内容读取成一个字符串 1、读取文件的指定行 &#xff08;1&#xff09;main函数中直接读 …

关于 Windows10 计算机丢失 MSVCP120.dll 的解决方法

今天学长跟平时一样打开电脑开始发布文章需要用到Adobe Photoshop CC 2018的时候居然给我来个Photoshop.exe-系统错误、无法启动此程序&#xff0c;因为计算机中丢失MSVCP120.dll 尝试重新安装该程序以解决此问题&#xff0c;安装上面的说明重新安装了我的Photoshop CC 打开还是…

移动端不居中问题/安卓和ios下line-height上下居中 css兼容问题

移动端开发过程&#xff0c;经常会写带0.5px边框角标类的样式&#xff0c;直接使用border设置0.5px边框&#xff0c;ios有些机型会出现显示不完整的情况。所以改用伪元素方法实现边框。代码如下&#xff1a; .comment-entry::after{content: ;position: absolute;left: 0;top: …

MySQL主从复制实现高可用性和负载均衡

大家好&#xff0c;我是咕噜铁蛋&#xff0c;今天我想和大家聊聊MySQL主从复制如何帮助我们实现高可用性和负载均衡。在如今的大数据时代&#xff0c;数据库的稳定性和性能成为了企业关注的重点&#xff0c;而MySQL主从复制正是解决这两个问题的重要工具。 一、MySQL主从复制简…