当前位置: 首页 > news >正文

数据结构与算法-哈希表

哈希算法

public class HashTable {

    // 定义一个内部类 Entry,用于存储键值对及其哈希码
    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 对象的数组,即哈希表的实际存储结构
    Entry[] table = new Entry[16];
    // 当前哈希表中存储的元素数量
    int size = 0;
    // 负载因子,默认值为 0.75
    float loadFactor = 0.75f;
    // 扩容阈值,当 size 超过该值时触发扩容操作
    int threshold = (int) (loadFactor * table.length);

    /**
     * 根据哈希码和键获取对应的值。
     *
     * @param hash 哈希码
     * @param key  键
     * @return 对应的值,如果找不到则返回 null
     */
    public Object get(int hash, Object key) {
        int idx = hash & (table.length - 1); // 计算索引位置
        if (table[idx] == null) {
            return null; // 如果该位置为空,则返回 null
        }
        Entry p = table[idx];
        while (p != null) { // 遍历链表
            if (p.key.equals(key)) {
                return p.value; // 找到匹配的键,返回对应的值
            }
            p = p.next; // 继续遍历链表中的下一个节点
        }
        return null; // 如果没有找到匹配的键,返回 null
    }

    /**
     * 将键值对插入哈希表中。
     *
     * @param hash  哈希码
     * @param key   键
     * @param value 值
     */
    public 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) {
                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 = p;
                    } else {
                        if (b != null) {
                            b.next = p;
                        } else {
                            bHead = p;
                        }
                        b = p;
                    }
                    p = p.next;
                }

                // 将重新分配后的链表插入到新的哈希表中
                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); // 更新扩容阈值
    }

    /**
     * 根据哈希码和键删除对应的键值对。
     *
     * @param hash 哈希码
     * @param key  键
     * @return 被删除的值,如果找不到则返回 null
     */
    public Object remove(int hash, Object key) {
        int idx = hash & (table.length - 1); // 计算索引位置
        if (table[idx] == null) {
            return null; // 如果该位置为空,则返回 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; // 否则更新前一个节点的 next 指针
                }
                size--; // 更新元素数量
                return p.value; // 返回被删除的值
            }
            prev = p; // 更新前一个节点
            p = p.next; // 继续遍历链表中的下一个节点
        }
        return null; // 如果没有找到匹配的键,返回 null
    }
}

两数之和-Leetcode 1

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

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

示例 2:

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

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]
public int[] twoSum(int[] nums, int target) {
	        HashMap<Integer, Integer> map=new HashMap<Integer, Integer>();
	        for (int i = 0; i < nums.length; i++) {
				int k=target-nums[i];
				if(map.containsKey(k)) {
					return new int[] {map.get(k),i};
				}
				map.put(nums[i], i);
			}
	        return null;

	    }

无重复字符的最长字串-Leetcode 3

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 

子串

 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

方法一:

public int lengthOfLongestSubstring(String s) {
	        HashMap<Character, Integer> map=new HashMap<Character, Integer>();
	        int begin=0;
	        int maxLength=0;
	        for (int end = 0; end < s.length(); end++) {
				char ch=s.charAt(end);
				if(map.containsKey(ch)) {
					begin=Math.max(begin, map.get(ch)+1);
					map.put(ch, end);
				}else{
					map.put(ch, end);
                }
				maxLength=Math.max(maxLength, end-begin+1);
			}
	        return maxLength;
	    }

 方法二:

public int lengthOfLongestSubstring(String s) {
		 int[] map=new int[128];
		 Arrays.fill(map, -1);
		 int begin = 0;
		 int maxLength = 0;
		 for (int end = 0; end < s.length(); end++) {
			 char ch = s.charAt(end);
			 if(map[ch]!=-1) {
				 begin=Math.max(begin, map[ch]+1);
				 map[ch]=end;
			 }else {
				 map[ch]=end;
			}
			 maxLength = Math.max(maxLength, end - begin + 1);
		 }
		 return maxLength;
	 }

字母异位词分组-Leetcode 49

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

输入: strs = [""]
输出: [[""]]

示例 3:

输入: strs = ["a"]
输出: [["a"]]

 方法一:

public List<List<String>> groupAnagrams(String[] strs) {
	        HashMap<String, List<String>> map=new HashMap<String, List<String>>();
	        for(String str :strs) {
	        	char[] chars=str.toCharArray();
	        	Arrays.sort(chars);
	        	String key=new String(chars);
	        	List<String> strings=map.computeIfAbsent(key, k -> new ArrayList<>());
	        	strings.add(str);
	        }
	        return new ArrayList<>(map.values());
	    }

方法二:

static class ArrayKey {
    int[] key = new int[26];

    public ArrayKey(String str) {
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            key[ch - 'a']++;
        }
    }

    @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 List<List<String>> groupAnagrams(String[] strs) {
    HashMap<ArrayKey, List<String>> map = new HashMap<>();
    for (String str : strs) {
        List<String> strings = map.computeIfAbsent(new ArrayKey(str), k -> new ArrayList<>());
        strings.add(str);
    }
    return new ArrayList<>(map.values());
}

找出出现一次的数字-Leetcode 136

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1 :

输入:nums = [2,2,1]

输出:1

示例 2 :

输入:nums = [4,1,2,1,2]

输出:4

示例 3 :

输入:nums = [1]

输出:1

方法一:

public int singleNumber(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];
}

方法二:

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

判断字母异位词-Leetcode 242

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的 

字母异位词

示例 1:

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

示例 2:

输入: s = "rat", t = "car"
输出: false

 public boolean isAnagram(String s, String t) {
        return Arrays.equals(getKey(s), getKey(t));
}

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

第一个不重复字符-Leetcode 387

给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。

示例 1:

输入: s = "leetcode"
输出: 0

示例 2:

输入: s = "loveleetcode"
输出: 2

示例 3:

输入: s = "aabb"
输出: -1

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

出现次数最多的单词-Leetcode 819

给你一个字符串 paragraph 和一个表示禁用词的字符串数组 banned ,返回出现频率最高的非禁用词。题目数据 保证 至少存在一个非禁用词,且答案 唯一 

paragraph 中的单词 不区分大小写 ,答案应以 小写 形式返回。

示例 1:

输入:paragraph = "Bob hit a ball, the hit BALL flew far after it was hit.", banned = ["hit"]
输出:"ball"
解释:
"hit" 出现了 3 次,但它是禁用词。
"ball" 出现了两次(没有其他单词出现这么多次),因此它是段落中出现频率最高的非禁用词。
请注意,段落中的单词不区分大小写,
标点符号会被忽略(即使它们紧挨着单词,如 "ball,"),
并且尽管 "hit" 出现的次数更多,但它不能作为答案,因为它是禁用词。

示例 2:

输入:paragraph = "a.", banned = []
输出:"a"
public String mostCommonWord(String paragraph, String[] banned) {
	        Set<String> banSet=Set.of(banned);
	        String[] split=paragraph.toLowerCase().split("[^A-Za-z]+");
	        HashMap<String, Integer> map=new HashMap<String, Integer>();
	        for(String key:split) {
	        	if(banSet.contains(key)) {
	        		continue;
	        	}
	        	map.compute(key, (k,v)-> v==null?1:v+1);
	        }
	        Integer 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;
	    }

相关文章:

  • resultMap 标签
  • 变相提高大模型上下文长度-RAG文档压缩-2.带早停机制的map-refine
  • pgsql用户和权限管理
  • 安卓基础(持续更新的笔记)
  • Kotlin Lambda
  • 2025年,如何选择IT监控平台
  • CEF132 编译指南 Linux 篇 - CEF 编译实战:构建 CEF(六)
  • 【Redis】下载安装Redis和Redis图形化界面工具教程(2024最新版本,史上最详细)
  • 石子合并
  • windows编译使用gtest
  • SpringBoot3 快速启动框架
  • VS2022中.Net Api + Vue 从创建到发布到IIS
  • Redis的常见数据结构
  • 内容中台驱动企业CMS架构优化与高效策略
  • springCloud-2021.0.9 之 GateWay 示例
  • Android Studio 打包App问题
  • 从0开始的操作系统手搓教程 4:做好准备,跳到加载器(Loader)
  • Word中设置表格在同一页
  • 深入解析SVG图片原理:从基础到高级应用
  • SpringCloud框架下的注册中心比较:Eureka与Consul的实战解析
  • 5月12日至13日北京禁飞“低慢小”航空器
  • 经济日报:降准降息,提前还房贷划算吗?
  • 售卖自制外挂交易额超百万元,一男子因提供入侵计算机系统程序被抓
  • 民生访谈|摆摊设点、公园搭帐篷、行道树飘絮,管理难题怎么解?
  • 金球看淡,不服就干!这是抬不起腿却昂着头的劳塔罗
  • 鸿蒙概念股强势上涨,鸿蒙电脑本月正式发布,生态链即将补全