Java(二十八)---哈希桶的模拟实现以及有关Map和Set的Oj题


前言

在上一篇博客中,我们学习了关于Map和Set的,Tree类型和Hash类型,下面我们学习哈希桶的模拟实现以及有关Map和Set的Oj题


1.哈希桶的模拟实现

在这里插入图片描述
这个就是哈希桶的模拟结构图,是由数组和节点,链表组成的。
其中那个扩容是最难的,等会好好进行讲解,现在开干。

1.1.准备工作

先把数组和节点准备好

public class HashBuck {
    static class Node{
        public int key;
        public int val;
        public Node next;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
}
public Node[] array = new Node[10];
public int usedSize;
public static double DEFAULT_LOAD_FACTOR = 0.75; // 负载因子

1.2 插入操作

1.第一步,先确定插入的位置(index = key % arr.length)。
2.第二步,判断该位置是否存在相同的key节点,如果存在,那么修改val的值,如果不存在,那么便使用头插法,进行存储
3.第三步,如果达到了负载因子的话,那么进行扩容。

public void put(int key,int val){
        int index = key % this.array.length;
        Node cur = array[index];
        while (cur != null){
            if (cur.key == key){
                cur.val = val;
                return;
            }
            cur = cur.next;
        }
        Node node = new Node(key,val);
        node.next = array[index];
        array[index] = node;
        this.usedSize ++ ;
        if (doLoadFactor() == DEFAULT_LOAD_FACTOR){
            resize();
        }
    }
private double doLoadFactor() {
        return  (usedSize*1.0/this.array.length);
    }

1.3.扩容操作

扩容并不是简单的数组扩容,而是要把每个节点都放在新的数组(把新的数组长度定为原来的2倍)下标中,例如上面的key = 34 的节点,通过 index = key % newArray.length,得到新的index,再把key = 34 的结点放到新数组指定的位置上。即可
在这里插入图片描述

    private void resize() {
        Node[] newArray = new Node[2*this.array.length];
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur != null){
                int index = cur.key % newArray.length;
                Node curN = cur.next;
                cur.next = newArray[index];
                newArray[index] = cur;
                cur = curN;
            }
        }
        array = newArray;
    }

1.4.获取元素的val值

 public int getVal(int key){
        int index = key % array.length;
        Node cur = array[index];
        while (cur != null){
            if (cur.key == key){
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }

大家抽空的时候,也可以再写一个关于key和value为泛型的一个哈希桶。

1.5.性能分析

虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的,也就是每个桶中的链表的长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是O(1) 。

1.6.和 java 类集的关系

  1. HashMap 和 HashSet 即 java 中利用哈希表实现的 Map 和 Set
  2. java 中使用的是哈希桶方式解决冲突的
  3. java 会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树)
  4. java 中计算哈希值实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key 的 equals 方法。所以如果要用自定义类作为HashMap 的 key 或者 HashSet 的值,必须覆写 hashCode 和 equals 方法,而且要做到 equals 相等的对象,hashCode 一定是一致的。

2.HashMap源码

在这里插入图片描述


3.Oj题

3.1.只出现一次的数字

在这里插入图片描述

这道题可以使用 异或(^)来做,但是既然我们学习了Map和Set,那么我们这会就Set来做这道题。
怎么做呢?
因为只有一个数字出现了一次,剩余的数字都出现了两次,
那么第一步,先遍历一遍数组,将数字存在hashset中,如果存在的话,在移除该元素,那么剩下的只有一个元素
第二步,再遍历一遍元素,hashset和数组中,存在相同的元素,那么直接返回即可,如果没有的话,返回-1即可。

class Solution {
    public int singleNumber(int[] nums) {
        HashSet<Integer> set = new HashSet();
        for (int i = 0;i<nums.length;i++){
            if (!set.contains(nums[i])){
                set.add(nums[i]);
            }else{
                set.remove(nums[i]);
            }
        }
        for (int i = 0; i < nums.length; i++) {
            if (set.contains(nums[i])) {
                return nums[i];
            }
        }
        return -1;
    }

}

3.2.随机链表的复制

在这里插入图片描述
在这里插入图片描述
这个是个深拷贝问题,新创建的链表的next指针,random指针指向的还是新的对应值的节点。

在这里插入图片描述
这样才是深拷贝,可以使用HashMap,用于存储新节点和老节点,之后再把每个节点的next引用和random引用连接上


class Solution {
    public Node copyRandomList(Node head) {
        HashMap<Node,Node> map = new HashMap<>();
        Node cur = head;
        while(cur != null){
            Node node = new Node(cur.val);
            map.put(cur,node);
            cur = cur.next;
        }
        cur = head;
        while (cur != null){
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head);
    }
}

3.3.宝石与石头

在这里插入图片描述
这道题比较简单,只需要我们把jewels中的每个字符都存在HashSet中,然后再把stones遍历一遍,如果HashSet中存在stones中的字符,那么count++,最后返回count即可。

class Solution {
    public int numJewelsInStones(String jewels, String stones) {
        HashSet<Character> set = new HashSet();
        int cnt = 0;
        for (int  i = 0;i<jewels.length();i++){
            char ch = jewels.charAt(i);
            set.add(ch);
        }
        for (int j = 0 ;j<stones.length();j++){
            char ch = stones.charAt(j);
            if (set.contains(ch)){
                cnt++;
            }
        }
        return cnt;
    }
}

3.4.旧键盘

在这里插入图片描述

在这里插入图片描述
其中< br/>是换行的意思,
第一步,因为需要返回的是英文大写,并且,只能返回一个,因此将第一句话(a) 和 第二句话(b)都变成大写(toUpperCase()),
第二步,将第二段话(str2)中每个单词都放在setAct(HashSet类型的)中,然后用setBroken(也是HashSet类型的)中,setBroken用于存储在str1中没有出现的单词,并且相同的只能存放一个,然后在打印出来。

import java.util.Scanner;
import java.util.HashSet;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNext()) { // 注意 while 处理多个 case
            String a = in.nextLine();
            String b = in.nextLine();
            func(a,b);
        }
    }
    public static void func(String str1,String str2){
       str1 = str1.toUpperCase();
        str2 = str2.toUpperCase();

        HashSet<Character> setAct = new HashSet<>();
        for (int i = 0; i < str2.length(); i++) {
            char ch = str2.charAt(i);
            setAct.add(ch);
        }

        HashSet<Character> setBroken = new HashSet<>();

        for (int i = 0; i < str1.length(); i++) {
            char ch = str1.charAt(i);
            if(!setAct.contains(ch) && !setBroken.contains(ch)) {
                setBroken.add(ch);
                System.out.print(ch);
            }
        }
    }

}

3.5.前K个高频单词

在这里插入图片描述
这道题不好做,首先需要使用HashMap来统计每个单词出现的次数,然后还要建立小根堆,后面就是堆排序了。,并且小根堆的比较器还需要重写,因为如果出现次数一样的单词,需要按照字典的先后顺序来排列,那么开干吧。

public List<String> topKFrequent(String[] words, int k) {
        //1.遍历words
        HashMap<String,Integer> map = new HashMap<>();
        for (String word:words){
            if (!map.containsKey(word)){
                map.put(word,1);
            }else {
                int val = map.get(word);
                map.put(word,val+1);
            }
        }
        // 2.第二步创建小根堆
        PriorityQueue<Map.Entry<String,Integer>>minHeap = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                if (o2.getValue().compareTo(o1.getValue())== 0){
                    return  o2.getKey().compareTo(o1.getKey());
                }
                return o1.getValue().compareTo(o2.getValue());
            }
        });
        //3.遍历map
        for(Map.Entry<String,Integer> entry:map.entrySet()){
            if (minHeap.size() < k){
                minHeap.offer(entry);
            }else {
                Map.Entry<String,Integer> top = minHeap.peek();
                if (top.getValue().compareTo(entry.getValue()) < 0){
                    minHeap.poll();
                    minHeap.offer(entry);
                }else if (top.getValue().compareTo(entry.getValue()) == 0) {
                    if (top.getKey().compareTo(entry.getKey())>0){
                        minHeap.poll();
                        minHeap.offer(entry);
                    }
                }
            }
        }
        List<String> list = new ArrayList<>();
        for(int i = 0;i<k;i++){
            Map.Entry<String,Integer> entry = minHeap.poll();
            list.add(entry.getKey());
        }
        Collections.reverse(list);
        return list;
    }

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值