算法-哈希表算法总结
2021-11-21 本文已影响0人
攻城老狮
1 哈希表模拟
思路:通过设计哈希表,模拟O(1)时间复杂度的哈希表。
// 剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器
// 设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构:insert(val):当元素 val 不存在时返回 true ,并向集合中插入该项,否则返回 false 。remove(val):当元素 val 存在时返回 true ,并从集合中移除该项,否则返回 false 。getRandom:随机返回现有集合中的一项。每个元素应该有 相同的概率 被返回。
class RandomizedSet {
private Map<Integer,Integer> map;
private List<Integer> list;
/** Initialize your data structure here. */
public RandomizedSet() {
map = new HashMap<>();
list = new ArrayList<>();
}
/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
public boolean insert(int val) {
if (map.containsKey(val)) return false;
map.put(val,list.size());
list.add(val);
return true;
}
/** Removes a value from the set. Returns true if the set contained the specified element. */
public boolean remove(int val) {
if (!map.containsKey(val)) return false;
int location = map.get(val);
map.put(list.get(list.size() - 1),location);
map.remove(val);
list.set(location,list.get(list.size() - 1));
list.remove(list.size() - 1);
return true;
}
/** Get a random element from the set. */
public int getRandom() {
Random random = new Random();
int location = random.nextInt(list.size());
return list.get(location);
}
}
// 剑指 Offer II 031. 最近最少使用缓存
// 运用所掌握的数据结构,设计和实现一个 LRU (Least Recently Used,最近最少使用) 缓存机制 。实现 LRUCache 类:LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
class LRUCache {
private Map<Integer,ListNode> map;
private ListNode head;
private ListNode tail;
private int capacity;
private static class ListNode {
int key;
int value;
ListNode prev;
ListNode next;
public ListNode (int key, int value) {
this.key = key;
this.value = value;
}
}
public LRUCache(int capacity) {
map = new HashMap<>();
head = new ListNode(-1,-1);
tail = new ListNode(-1,-1);
head.next = tail;
tail.prev = head;
this.capacity = capacity;
}
public int get(int key) {
ListNode node = map.get(key);
if (node == null) return -1;
moveToTail(node,node.value);
return node.value;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
moveToTail(map.get(key),value);
} else {
if (map.size() == capacity) {
ListNode toBeDeleted = head.next;
deleteNode(toBeDeleted);
map.remove(toBeDeleted.key);
}
ListNode node = new ListNode(key,value);
insertToTail(node);
map.put(key,node);
}
}
private void moveToTail(ListNode node,int value) {
deleteNode(node);
node.value = value;
insertToTail(node);
}
private void deleteNode(ListNode node) {
node.next.prev = node.prev;
node.prev.next = node.next;
}
private void insertToTail(ListNode node) {
tail.prev.next = node;
node.prev = tail.prev;
node.next = tail;
tail.prev = node;
}
}
2 数组作为哈希表
思路:数组就是简单的哈希表,数组的大小是受限的。
// 242. 有效的字母异位词
// 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
int[] hash = new int[26];
for (int i = 0; i < s.length(); i++) {
hash[s.charAt(i) - 'a']++;
hash[t.charAt(i) - 'a']--;
}
return allZero(hash);
}
private boolean allZero(int[] hash) {
for (int item : hash) {
if (item != 0) return false;
}
return true;
}
}
// 1002. 查找共用字符
// 给你一个字符串数组 words ,请你找出所有在 words 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。
class Solution {
public List<String> commonChars(String[] words) {
List<String> res = new ArrayList<>();
int[] hash = new int[26];
for (int i = 0; i < words[0].length(); i++) {
hash[words[0].charAt(i) - 'a']++;
}
for (int i = 1; i < words.length; i++) {
int[] tmpHash = new int[26];
for (int j = 0; j < words[i].length(); j++) {
tmpHash[words[i].charAt(j) - 'a']++;
}
for (int j = 0; j < 26; j++) {
hash[j] = Math.min(hash[j],tmpHash[j]);
}
}
for (int i = 0; i < 26; i++) {
while (hash[i] > 0) {
res.add(String.valueOf((char)(i + 'a')));
hash[i]--;
}
}
return res;
}
}
// 剑指 Offer II 032. 有效的变位词
// 给定两个字符串 s 和 t ,编写一个函数来判断它们是不是一组变位词(字母异位词)。注意:若 s 和 t 中每个字符出现的次数都相同且字符顺序不完全相同,则称 s 和 t 互为变位词(字母异位词)。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length() || s.equals(t)) return false;
int[] hash = new int[26];
for (int i = 0; i < s.length(); i++) {
hash[s.charAt(i) - 'a']++;
hash[t.charAt(i) - 'a']--;
}
return allZero(hash);
}
private boolean allZero(int[] hash) {
for (int item : hash) {
if (item != 0) {
return false;
}
}
return true;
}
}
// 剑指 Offer II 034. 外星语言是否排序
// 某种外星语也使用英文小写字母,但可能顺序 order 不同。字母表的顺序(order)是一些小写字母的排列。给定一组用外星语书写的单词 words,以及其字母表的顺序 order,只有当给定的单词在这种外星语中按字典序排列时,返回 true;否则,返回 false。
class Solution {
public boolean isAlienSorted(String[] words, String order) {
int[] hash = new int[order.length()];
for (int i = 0; i < order.length(); i++) {
hash[order.charAt(i) - 'a'] = i;
}
for (int i = 0; i < words.length - 1; i++) {
if (!isSorted(words[i],words[i + 1],hash)) return false;
}
return true;
}
private boolean isSorted(String str1,String str2,int[] hash) {
int i = 0;
for (; i < str1.length() && i < str2.length(); i++) {
char ch1 = str1.charAt(i);
char ch2 = str2.charAt(i);
if (hash[ch1 - 'a'] < hash[ch2 - 'a']) {
return true;
} else if (hash[ch1 - 'a'] > hash[ch2 - 'a']) {
return false;
}
}
return i == str1.length();
}
}
// 剑指 Offer II 035. 最小时间差
// 给定一个 24 小时制(小时:分钟 "HH:MM")的时间列表,找出列表中任意两个时间的最小时间差并以分钟数表示。
class Solution {
public int findMinDifference(List<String> timePoints) {
if (timePoints.size() > 1440) return 0;
boolean[] hash = new boolean[1440];
for (int i = 0; i < timePoints.size(); i++) {
String[] timePoint = timePoints.get(i).split(":");
int min = Integer.parseInt(timePoint[0]) * 60 + Integer.parseInt(timePoint[1]);
if (hash[min]) return 0;
hash[min] = true;
}
return helper(hash);
}
private int helper(boolean[] hash) {
int minDiff = hash.length - 1;
int prev = Integer.MIN_VALUE;
int first = Integer.MAX_VALUE;
int last = Integer.MIN_VALUE;
for (int i = 0; i < hash.length; i++) {
if (hash[i]) {
if (prev != Integer.MIN_VALUE) {
minDiff = Math.min(minDiff,i - prev);
}
prev = i;
first = Math.min(first,i);
last = Math.max(last,i);
}
}
minDiff = Math.min(minDiff,first + hash.length - last);
return minDiff;
}
}
3 Set作为哈希表
思路:输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序。使用数组来做哈希的题目,是因为题目都限制了数值的大小。而若没有限制数值的大小,就无法使用数组来做哈希表了。而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
// 349. 两个数组的交集
// 给定两个数组,编写一个函数来计算它们的交集。
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
return new int[0];
}
Set<Integer> tmpSet = new HashSet<>();
Set<Integer> resSet = new HashSet<>();
for (int num : nums1) {
tmpSet.add(num);
}
for (int num : nums2) {
if (tmpSet.contains(num)) {
resSet.add(num);
}
}
int[] res = new int[resSet.size()];
int index = 0;
for (int num : resSet) {
res[index++] = num;
}
return res;
}
}
// 202. 快乐数
// 编写一个算法来判断一个数 n 是不是快乐数。「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。如果 n 是快乐数就返回 true ;不是,则返回 false 。
class Solution {
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
while (n != 1 && !set.contains(n)) {
set.add(n);
n = getNextNum(n);
}
return n == 1;
}
private int getNextNum(int n) {
int res = 0;
while (n > 0) {
int tmp = n % 10;
res += tmp * tmp;
n /= 10;
}
return res;
}
}
4 Map作为哈希表
思路:数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。set是一个集合,里面放的元素只能是一个key,而若不仅要判断y是否存在而且还要记录y的其他信息,所以set 也不能用,需要使用哈希表。
// 1. 两数之和
// 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if (nums == null || nums.length == 0) return res;
Map<Integer,Integer> hash = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int tmp = target - nums[i];
if (hash.containsKey(tmp)) {
res[0] = hash.get(tmp);
res[1] = i;
break;
}
hash.put(nums[i],i);
}
return res;
}
}
// 454. 四数相加 II
// 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:0 <= i, j, k, l < n nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int res = 0;
Map<Integer,Integer> hash = new HashMap<>();
for (int num1 : nums1) {
for (int num2 : nums2) {
int tmp = num1 + num2;
hash.put(tmp,hash.getOrDefault(tmp,0) + 1);
}
}
for (int num3 : nums3) {
for (int num4 : nums4) {
int tmp = 0 - (num3 + num4);
if (hash.containsKey(tmp)) {
res += hash.get(tmp);
}
}
}
return res;
}
}
// 剑指 Offer II 033. 变位词组
// 给定一个字符串数组 strs ,将 变位词 组合在一起。 可以按任意顺序返回结果列表。注意:若两个字符串中每个字符出现的次数都相同,则称它们互为变位词。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>> hash = new HashMap<>();
for (String str : strs) {
char[] chs = str.toCharArray();
Arrays.sort(chs);
String sorted = new String(chs);
hash.putIfAbsent(sorted,new LinkedList<>());
hash.get(sorted).add(str);
}
return new LinkedList<>(hash.values());
}
}