哈希表实现[很详细!]

目录

哈希表

定义节点类

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人工智能大模型培训老师叶梓 探索知识库问答中的查询图生成:处理多跳复杂问题的新方法

在人工智能领域&#xff0c;基于知识库的问答&#xff08;KBQA&#xff09;技术正变得越来越重要。它使得机器能够理解自然语言问题&#xff0c;并从结构化的知识库中检索答案。然而&#xff0c;面对多跳复杂问题&#xff0c;传统的KBQA方法往往力不从心。近期&#xff0c;研究…

账号安全基本措施1

一、系统账号清理 1.1 将用户设置为无法登录 useradd -s /sbin/nologin lisi shell类型设置为/sbin/nologin用户将无法使用bash或其他shell来登录系统。 1.2 锁定用户。passwd -l 用户名 正常情况下是可以送普通用户切换到其他普通用户的 当锁定密码后passwd -l lisi就用普…

第22天:安全开发-PHP应用留言板功能超全局变量数据库操作第三方插件引用

第二十二天 一、PHP留言板前后端功能实现 开发环境&#xff1a; DW PHPStorm PhpStudy Navicat Premium DW : HTML&JS&CSS开发 PHPStorm : 专业PHP开发IDE PhpStudy &#xff1a;Apache MYSQL环境 Navicat Premium: 全能数据库管理工具 二、数据库创建&架…

【解决】echarts条形图纵坐标显示不全

先说结论&#xff1a; option:{...grid: {containLabel: true},... }这个属性是控制整体的坐标标签的。加上这个就可以显示完整了。然后再根据其他属性调整标签的字体、颜色之类的 yAxis : [{...axisLabel:{width:100,overflow:break,truncate:...,color:red,fontSize:10,},..…

JavaScript进阶部分知识总结

作用域 局部作用域 作用域规定了变量能够被访问的范围&#xff0c;离开了这个范围变量就不能被访问作用域分为&#xff1a;局部作用域和全局作用域 局部作用域分为函数作用域和块作用域 1.函数作用域&#xff1a; 在函数内部声明的变量只能在函数内部被访问&#xff0c;外…

AWD线下攻防万字最完整战术(记第一届“长城杯”半决赛战术)

目录 准备阶段 1.登录比赛平台&#xff08;获取资产&#xff09; 查看账号账号修改 服务器SSH口令mysqlWEB服务口令(后台密码)数据库后台管理员密码 账号用户检查 2.dump源码&#xff08;方便应急响应恢复靶机&#xff09; 网站源码备份 压缩文件解压文件备份到服务器本地上传…

这10款VS Code神仙插件,嵌入式程序员必备

大家好&#xff0c;我是知微&#xff01; 嵌入式软件开发工程师平时可能更多的是使用Source Insight、Keil、IAR来阅读代码&#xff0c;写代码。 VSCode大家都听说过&#xff0c;功能十分强大&#xff0c;而且免费&#xff01; 或许是因为这款软件上手有一定的学习成本&…

css:echarts渐变色转换为css渐变色

通过一个下拉框来选择渐变类型&#xff0c;为了简化&#xff0c;我设置了三种&#xff1a;水平方向的渐变、垂直方向的渐变和径向渐变用&#xff0c;表格来配置echarts渐变色的百分比位置和颜色。 config是表格里的数据格式如下&#xff1a; offset是百分比位置&#xff0c;co…

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

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

[C++][算法基础]求组合数(IV)

输入 &#x1d44e;,&#x1d44f;&#xff0c;求 的值。 注意结果可能很大&#xff0c;需要使用高精度计算。 输入格式 共一行&#xff0c;包含两个整数 &#x1d44e; 和 &#x1d44f;。 输出格式 共一行&#xff0c;输出 的值。 数据范围 1≤b≤a≤5000 输入样例…

一线实战:国产数据库Mogdb双网卡同步最佳实践

前言 大家都知道Oracle数据库无论是单机还是RAC集群在进行生产部署实施时&#xff0c;我们都会对网卡做冗余考虑&#xff0c;使用双网卡&#xff0c;比如public、心跳网络。这样的目的主要是为了安全&#xff0c;避免单点故障。当然双网卡Bond不仅是可以做主备还可以支持负载均…

安装mysql的流程

安装mysql的步骤 安装流程 [rootlocalhost z]# cd /mnt/share/share[rootlocalhost share]# ll[rootlocalhost share]# cp mysql157-community-release-el7-10.noarch.rmp /usr/localcp: cannot stat ‘mysql157-community-release-el7-10.noarch.rmp’: No such file or direc…

企业车辆管理系统平台是做什么的?

企业车辆管理系统平台是一种综合性的管理系统&#xff0c;它主要集车辆信息管理、车辆调度、车辆维修、油耗管理、驾驶员管理以及报表分析等多种功能于一体。通过这个平台&#xff0c;企业可以实现对车辆的全面管理&#xff0c;优化车辆使用效率&#xff0c;降低运营成本&#…

JavaWeb开发06-原理-Spring配置优先级-Bean管理-SpringBoot原理-Maven继承和聚合-私服

一、Spring配置优先级 不同配置文件&#xff0c;配置同一个属性谁有效 properties>yml>yaml 命令行参数>Java系统属性 项目打包后要改变属性&#xff1a; 红色是Java系统属性&#xff0c;绿色是命令行参数 ‘ 二、Bean管理 1.获取bean 获取IOC容器&#xff1a;ap…

linux之进程通信

目录 一、进程通信介绍 1.目的 2.发展 3.进程通信是什么&#xff0c;怎么通信&#xff1f; 二、管道 1.介绍 2.匿名管道 1.单向通信管道原理 2.代码实现 3.管道特征 4.管道的四种情况 5.管道的应用场景 使用管道实现一个简易版本的进程池 3.命名管道 1.思考 2.…

了解IPS和IDS:这5个差异将改变你的安全观念!

IPS 代表 入侵防御系统&#xff08;Intrusion Prevention System&#xff09;&#xff0c;它是 IDS 的进一步发展&#xff0c;不仅具备检测攻击的能力&#xff0c;还能在检测到攻击后主动采取措施阻止攻击。IPS 通常部署在防火墙和网络设备之间&#xff0c;能够深度感知并检测流…

ubuntu18.04与windows文件互传

目录 window下载Xftp软件ubuntu上的配置windows端Xftp软件的使用 window下载Xftp软件 下载&#xff1a;家庭/学校免费版 安装教程推荐下面的文章 xftp7免费版安装教程&#xff08;详细&#xff09; ubuntu上的配置 在进入系统后&#xff0c;确保有网络连接的情况下按Ctrl A…

cookie与session区别和联系

在Web应用中&#xff0c;HTTP协议是无状态的&#xff0c;每次请求都是独立的&#xff0c;服务器无法直接识别一个用户的不同请求之间的关联。这就导致了如果我们希望在一个会话中保持一些数据的状态&#xff0c;比如用户的身份认证信息、购物车内容等&#xff0c;就需要借助Coo…

golang本地缓存库之bigcache

1. 前言 上周工作之余逛github看到一个本地缓存库bigcache&#xff0c;这个是allegro公司开源的一个项目&#xff0c;主要是用于本地缓存使用&#xff0c;根据他们的博客说明&#xff0c;他们编写这个库最初的目的就是实现一个非常快速的缓存服务。 看了下bigcache这个库的源…

[StartingPoint][Tier2]Base

Task 1 Which two TCP ports are open on the remote host? (远程服务器开放了哪两个TCP端口?) $ nmap -sC -sV 10.129.234.232 22,80 Task 2 What is the relative path on the webserver for the login page? (相关的登录页面路径是什么?) /login/login.php Task 3 …