哈希集合哈希映射
哈希集合和哈希映射是经常使用的两个类。
HashMap设计键的总结 https://leetcode-cn.com/leetbook/read/hash-table/xxavl2/
当字符串 / 数组中每个元素的顺序不重要时,可以使用排序后的字符串 / 数组作为键。
如果只关心每个值的偏移量,通常是第一个值的偏移量,则可以使用偏移量作为键。
在树中,你有时可能会希望直接使用 TreeNode 作为键。 但在大多数情况下,采用子树的序列化表述可能是一个更好的主意。
在矩阵中,你可能希望使用行索引或列索引作为键。
在数独中,可以将行索引和列索引组合来标识此元素属于哪个块。( rowIndex * block行数量 / block行数量 + colIndex / block列数量)
有时,在矩阵中,您可能希望将值聚合在同一对角线中。
1. 同构字符串
给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
示例 1:
输入:s = "egg", t = "add"
输出:true
示例 2:
输入:s = "foo", t = "bar"
输出:false
示例 3:
输入:s = "paper", t = "title"
输出:true
提示:
可以假设 s 和 t 长度相同。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/hash-table/xhjvbj/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解题思路:把源字符串中的每个字符和目的字符串的每个字符串做映射,一一对应。通过HashMap来映射源到目的,通过HashSet来判断目的字符已经存在映射。 此题可以扩展为单词pattern。
class Solution { public boolean isIsomorphic(String s, String t) { if(s.length() != t.length()) { return false; } Mapfor(int i = 0; i < s.length(); i++){ char sch = s.charAt(i); char tch = t.charAt(i); if(dict.containsKey(sch)) { char och = dict.get(sch); if(och != tch) { return false; } } else { if(used.contains(tch)) { return false; } dict.put(sch, tch); used.add(tch); } } return true; } } 2. 有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
数独部分空格内已填入了数字,空白格用 '.' 表示。
示例 1:
输入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: true
示例 2:
输入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 '.' 。
给定数独永远是 9x9 形式的。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/hash-table/xxpit5/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
方法1:
解题思路: 每一行,每一列, 每个3*3 box都定义一个HashMap来存储元素,如果其中有一个hashMap中出现重复数字,则返回false, 否则返回true. Box的下标通过 (rowIndex / 3 * 3 + colIndex / 3) 获得。
class Solution { public boolean isValidSudoku(char[][] board) { HashMapfor(int i = 0 ; i < 9; i++) { rowMaps[i] = new HashMap<>(); colMaps[i] = new HashMap<>(); boxMaps[i] = new HashMap<>(); } for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { if(board[i][j] == '.') { continue; } int num = board[i][j]; HashMap
rowMap.put(num, rowMap.getOrDefault(num, 0) + 1); colMap.put(num, colMap.getOrDefault(num,0) + 1); boxMap.put(num, boxMap.getOrDefault(num, 0) + 1); if(rowMap.get(num) > 1 || colMap.get(num) > 1 || boxMap.get(num) > 1) { return false; } } } return true; } } 方法2: 定义9*9的行,列,box布尔数组,用来存储是否是有过该数字。如果出现两次则返回false. class Solution { public boolean isValidSudoku(char[][] board) { boolean[][] rowFill = new boolean[9][9]; boolean[][] colFill = new boolean[9][9]; boolean[][] boxFill = new boolean[9][9];
for(int i = 0; i < 9; i++) { for(int j = 0; j < 9; j++) { if(board[i][j] == '.') { continue; }
int num = board[i][j] - '1'; if(rowFill[i][num] || colFill[j][num] || boxFill[i/3 * 3 + j/3][num]) { return false; }
rowFill[i][num] = true; colFill[j][num] = true; boxFill[i/3 * 3 + j/3][num] = true;
} } return true; } } 3. 我的日程安排
实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内没有其他安排,则可以存储这个新的日程安排。
MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。
当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生重复预订。
每次调用 MyCalendar.book方法时,如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。
请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)
示例 1:
MyCalendar();
MyCalendar.book(10, 20); // returns true
MyCalendar.book(15, 25); // returns false
MyCalendar.book(20, 30); // returns true
解释:
第一个日程安排可以添加到日历中. 第二个日程安排不能添加到日历中,因为时间 15 已经被第一个日程安排预定了。
第三个日程安排可以添加到日历中,因为第一个日程安排并不包含时间 20 。
说明:
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/my-calendar-i
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:把每个日程放入TreeMap, 开始时间作为key, 结束时间作为值。TreeNap具有天然的排序功能。每次放入日程时找到开始时间的上下日程进行比价。
class MyCalendar { TreeMappublic MyCalendar() { treeMap = new TreeMap<>(); }
public boolean book(int start, int end) { Integer preStart = treeMap.floorKey(start); Integer postStart = treeMap.ceilingKey(start);
if((preStart == null || treeMap.get(preStart) <= start) && (postStart == null || end <= postStart )) { treeMap.put(start, end); return true; }
return false; } } 4. 我的日程安排2
实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。
MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。
当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订。
每次调用 MyCalendar.book方法时,如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。
请按照以下步骤调用MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)
示例:
MyCalendar();
MyCalendar.book(10, 20); // returns true
MyCalendar.book(50, 60); // returns true
MyCalendar.book(10, 40); // returns true
MyCalendar.book(5, 15); // returns false
MyCalendar.book(5, 10); // returns true
MyCalendar.book(25, 55); // returns true
解释:
前两个日程安排可以添加至日历中。 第三个日程安排会导致双重预订,但可以添加至日历中。
第四个日程安排活动(5,15)不能添加至日历中,因为它会导致三重预订。
第五个日程安排(5,10)可以添加至日历中,因为它未使用已经双重预订的时间10。
第六个日程安排(25,55)可以添加至日历中,因为时间 [25,40] 将和第三个日程安排双重预订;
时间 [40,50] 将单独预订,时间 [50,55)将和第二个日程安排双重预订。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/my-calendar-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:要检查是否是三重预定,先找出双重预定的时间点,再用日程安排去和双重预定时间比较。
方法1:
public class MyCalendarTwo {class MyCalendarTwo {
private ListcalendarList;
private ListoverlapList;
public MyCalendarTwo() {
calendarList = new ArrayList<>();
overlapList = new ArrayList<>();
}
public boolean book(int start, int end) {
for(int[] cal : overlapList) {
if(start < cal[1] && end > cal[0]) {
return false;
}
}
for(int[] cal : calendarList) {
if(start < cal[1] && end > cal[0]) {
overlapList.add(new int[] {Math.max(start, cal[0]), Math.min(end, cal[1])});
}
}
int[] calendar = new int[2];
calendar[0] = start;
calendar[1] = end;
calendarList.add(calendar);
return true;
}
}
方法2:使用TreeMap存储日程的开始和结束时间的key,开始记为1,结束记为-1. TreeMap是排序的,所以日程都是按时间进行的。如果有3个在同时开的会议,则返回false,否则为true.
TreeMap
public MyCalendarTwo() { calendars = new TreeMap<>(); }
public boolean book(int start, int end) { calendars.put(start, calendars.getOrDefault(start,0) + 1); calendars.put(end, calendars.getOrDefault(end, 0) - 1);
int active = 0; for(int val : calendars.values()) { active += val; if(active >= 3) { //如果超过3个则把添加的日程删掉。 calendars.put(start, calendars.get(start) - 1); calendars.put(end, calendars.get(end) + 1); if(calendars.get(start) == 0) { calendars.remove(start); } return false; } }
return true; } } 4. 我的日程安排3
实现一个 MyCalendar 类来存放你的日程安排,你可以一直添加新的日程安排。
MyCalendar 有一个 book(int start, int end)方法。它意味着在start到end时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。
当 K 个日程安排有一些时间上的交叉时(例如K个日程安排都在同一时间内),就会产生 K 次预订。
每次调用 MyCalendar.book方法时,返回一个整数 K ,表示最大的 K 次预订。
请按照以下步骤调用MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)
示例 1:
MyCalendarThree();
MyCalendarThree.book(10, 20); // returns 1
MyCalendarThree.book(50, 60); // returns 1
MyCalendarThree.book(10, 40); // returns 2
MyCalendarThree.book(5, 15); // returns 3
MyCalendarThree.book(5, 10); // returns 3
MyCalendarThree.book(25, 55); // returns 3
解释:
前两个日程安排可以预订并且不相交,所以最大的K次预订是1。
第三个日程安排[10,40]与第一个日程安排相交,最高的K次预订为2。
其余的日程安排的最高K次预订仅为3。
请注意,最后一次日程安排可能会导致局部最高K次预订为2,但答案仍然是3,原因是从开始到最后,时间[10,20],[10,40]和[5,15]仍然会导致3次预订。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/my-calendar-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路: 使用TreeMap,记录下同时段开会的最大值。
class MyCalendarThree { TreeMappublic MyCalendarThree() { calendars = new TreeMap<>(); } public int book(int start, int end) { calendars.put(start, calendars.getOrDefault(start, 0) + 1); calendars.put(end, calendars.getOrDefault(end, 0) -1); int count = 0; int result = 0; for(int val : calendars.values()) { count += val; result = Math.max(count, result); } return result; } } 5. 四数相加
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
例如:
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/4sum-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:把四数分为两两相加后再相加。两两相加的和作为key,并统计次数放入hashMap. 如果另外两两相加的和为-key,这找到组合。
class Solution { public int fourSumCount(int[] A, int[] B, int[] C, int[] D) { HashMapfor(int v : A) { for(int k : B) { abMap.put(v + k, abMap.getOrDefault(v + k, 0) + 1); } } int answer = 0;
for(int v : C) { for(int k : D) { if(abMap.containsKey(-v-k)) { answer += abMap.get(-v-k); } } } return answer; } } 6. 常数时间插入、删除和获取随机元素
设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构。
insert(val):当元素 val 不存在时,向集合中插入该项。
remove(val):元素 val 存在时,从集合中移除该项。
getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。
示例 :
// 初始化一个空的集合。
RandomizedSet randomSet = new RandomizedSet();
// 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomSet.insert(1);
// 返回 false ,表示集合中不存在 2 。
randomSet.remove(2);
// 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomSet.insert(2);
// getRandom 应随机返回 1 或 2 。
randomSet.getRandom();
// 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomSet.remove(1);
// 2 已在集合中,所以返回 false 。
randomSet.insert(2);
// 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
randomSet.getRandom();
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/hash-table/xx0zpt/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解题思路:此题的关键点是要求在O(1)下操作。添加删除,hashMap都能O(1),但是random没法达到目的,所以需要List来存储元素,hashmap做位置索引。
class RandomizedSet { HashMapint index = set.get(val); list.set(index,list.get(list.size() - 1)); set.put(list.get(list.size() - 1),index); list.remove(list.size() - 1); set.remove(val);
return true; } /** Get a random element from the set. */ public int getRandom() { return list.get(random.nextInt(list.size())); } }
/** * Your RandomizedSet object will be instantiated and called as such: * RandomizedSet obj = new RandomizedSet(); * boolean param_1 = obj.insert(val); * boolean param_2 = obj.remove(val); * int param_3 = obj.getRandom(); */ 7. 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
HashMap
for(int num : nums) {
numCount.put(num, numCount.getOrDefault(num, 0) + 1);
}
PriorityQueue
return num1[1] - num2[1];
});
for(Map.Entry
int num = entry.getKey();
int count = entry.getValue();
if(preKNum.size() < k) {
preKNum.offer(new int[]{num, count});
continue;
}
if(preKNum.peek()[1] < count) {
preKNum.poll();
preKNum.add(new int[]{num, count});
}
}
int size = preKNum.size();
int[] result = new int[size];
for(int i = size -1 ; i >=0; i--){
result[i] = preKNum.poll()[0];
}
return result;
}
}
8. 寻找重复的子树
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
示例 1:
1
/ \
2 3
/ / \
4 2 4
/
4
下面是两个重复的子树:
2
/
4
和
4
因此,你需要以列表的形式返回上述重复子树的根结点。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/hash-table/xxm0i6/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
思路:把每条路径和子路径都作为key存入HashMap。重复出现的路径进行技计数统计。空节点返回#.
class Solution { public ListfindDupSubTrees(root, treeMap, result); return result; }
private String findDupSubTrees(TreeNode node, Map
treeMap.put(serial, treeMap.getOrDefault(serial, 0) + 1); if(treeMap.get(serial) == 2) { result.add(node); } return serial; } } 9. 最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
作者:Krahets
链接:https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dgr0c/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
return maxLength; } }
763. 划分字母区间
字符串S
由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
示例:
输入:S = "ababcbacadefegdehijhklij" 输出:[9,7,8] 解释: 划分结果为 "ababcbaca", "defegde", "hijhklij"。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
提示:
S
的长度在[1, 500]
之间。S
只包含小写字母'a'
到'z'
。
class Solution { public ListpartitionLabels(String s) { List result = new ArrayList (); Map charMap = new HashMap<>(); char[] chars = s.toCharArray(); for(int i = 0; i < chars.length; i++) { charMap.put(chars[i], i); } int start = 0; int end = -1; for(int i = 0; i < chars.length; i++) { int lastIndex = charMap.get(chars[i]); if(lastIndex < end) { continue; } end = lastIndex; if(i == end) { result.add(end - start + 1); start = end + 1; } } return result; } }
1711. 大餐计数
大餐 是指 恰好包含两道不同餐品 的一餐,其美味程度之和等于 2 的幂。
你可以搭配 任意 两道餐品做一顿大餐。
给你一个整数数组 deliciousness
,其中 deliciousness[i]
是第 i??????????
???? 道餐品的美味程度,返回你可以用数组中的餐品做出的不同 大餐 的数量。结果需要对 109 + 7
取余。
注意,只要餐品下标不同,就可以认为是不同的餐品,即便它们的美味程度相同。
示例 1:
输入:deliciousness = [1,3,5,7,9] 输出:4 解释:大餐的美味程度组合为 (1,3) 、(1,7) 、(3,5) 和 (7,9) 。 它们各自的美味程度之和分别为 4 、8 、8 和 16 ,都是 2 的幂。
class Solution { public int countPairs(int[] deliciousness) { int mod = 1000000007; int len = deliciousness.length; int[] checks = new int[22]; for(int i = 0; i < 22; i++) { checks[i] = (1 << i); } HashMapmap = new HashMap<>(); int pairs = 0; for(int j = 0; j < len; j++) { int num = deliciousness[j]; for(int k = 0; k < 22; k++) { int count = map.getOrDefault(checks[k] - num, 0); pairs = (pairs + count) % mod; } map.put(num, map.getOrDefault(num, 0) + 1); } return pairs; } }