一、基础理论
哈希表是由 HashTable
实现的,相关的实现有HashMap
、HashSet
。是根据关键码值(Key value)而直接进行访问的数据结构。
二、常用方法
2.1. HashMap常用方法
2.1.1 创建
// 不指定容量,默认初始容量为16
HashMap<String, String> map1 = new HashMap<>();
HashMap<String, String> map2 = Maps.newHashMap();
// 构造一个初始容量为8的HashMap
HashMap<String, String> map = new HashMap<>(8);
2.1.2 遍历
EntrySet遍历
// EntrySet for循环遍历
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// EntrySet Iterator迭代器遍历
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println(entry.getKey() + ":" + entry.getValue());
}
keySet遍历
// keySet for循环遍历
for (String key : map.keySet()) {
System.out.println(key + ":" + map.get(key));
}
// keySet Iterator迭代器遍历
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
System.out.println(key + ":" + map.get(key));
}
2.1.3 其他常用方法
put(); //将键/值对添加到 hashMap 中
putAll(); //将所有键/值对添加到 hashMap 中
remove(); //删除 hashMap 中指定键 key 的映射关系
containsKey(); //检查 hashMap 中是否存在指定的 key 对应的映射关系。
containsValue(); //检查 hashMap 中是否存在指定的 value 对应的映射关系。
values(); //获取 hashMap 中所有 value 值的集合。
size(); //计算 hashMap 中键/值对的数量
get(); //获取指定 key 对应对 value
getOrDefault(); // 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
isEmpty(); //判断 hashMap 是否为空
replace(); //替换 hashMap 中是指定的 key 对应的 value。
replaceAll(); //将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。
2.2. HashSet常用方法
HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。
2.2.1 创建
// 不指定容量,默认初始容量为16
HashSet<String> set1 = new HashSet<>();
HashSet<String> set2 = Sets.newHashSet();
// 构造一个初始容量为8的HashSet
HashSet<String> set = new HashSet<>(8);
2.2.2 遍历
for 循环遍历
//for 循环遍历
for (String str : set) {
System.out.println(str);
}
Iterator 迭代器遍历
//Iterator 迭代器遍历
Iterator<String> it = set.iterator();
while(it.hasNext()) {
String str = it.next();
System.out.println(str);
}
2.2.3 其他常用方法
add(); //添加元素,同一个元素被添加了两次,它在集合中也只会出现一次,因为集合中的每个元素都必须是唯一的。
contains(); // 判断元素是否存在于集合当中
remove(); // 删除集合中的元素,删除成功返回 true,否则为 false
clear(); //删除集合中所有元素
size(); //计算 HashSet 中的元素数量
for (String i : sites) //可以使用 for-each 来迭代 HashSet 中的元素。
isEmpty(); // 判断集合是否为空
toArray(); //将内容转到数组中
2.3 ArrayList常用方法
ArrayList类是一个动态数组类型,可以随时从链表中添加或删除一个元素。ArrayList实现了List接口并且可以动态改变大小的。
2.3.1 创建
// 默认构造函数创建
ArrayList<String> list1 = new ArrayList<>();
ArrayList<String> list2 = Lists.newArrayList();
// 使用包含初始元素的构造函数创建
ArrayList<String> list = new ArrayList<>(Arrays.asList("s1", "s2", "s3"));
2.3.2 遍历
for 循环遍历
// for 循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
增强for循环
//增强for循环
for(String s: list) {
System.out.println(s);
}
iterator 迭代器遍历
// iterator 迭代器遍历
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
2.3.3 其他常用方法
add("a"); //将a加入到list,依次按照顺序向ArrayList中添加数据
add(N, "E"); //在第N个数据后面添加一个数据
addAll(); //将一个ArrayList中的所有数据添加到另外一个ArraList中
remove(2); //按照位置删除单个数据
remove("d"); //按照内容删除单个数据
removeAll(); //按照集合同时删除多个数据
clear(); //清空list
get(int index); //查询指定位置的元素
set(int index, E element); //修改元素
isEmpty(); //判断是否为空
size(); //获取该数组的长度(包含元素的个数)
indexOf(); //第一次出现位置
lastIndexOf(); //最后一次出现位置
toArray(); //转化成数组
三、leetcode例题详解
3.1 两数之和
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]
解法一:暴力解法
双重遍历寻找两数之和为目标值的下标
// 1. 暴力解法
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
for(int i = 0; i < nums.length; i++){
for(int j = i+1; j < nums.length; j++){
if(nums[i] + nums[j] == target){
result[0] = i;
result[1] = j;
return result;
}
}
}
return null;
}
解法二: 哈希解法
遍历数组,通过在hashMap中寻找当前值距离目标和的差值来判断是否符合结果。同时将遍历过的数组元素存入hashMap,供后续的遍历查找。
// 2. 哈希解法
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for(int i = 0; i < nums.length; i++){
if(map.containsKey(target-nums[i])){
return new int[]{i, map.get(target-nums[i])};
}
map.put(nums[i], i);
}
return null;
}
3.2 字母异位词分组
leetcode题目链接:49. 字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
解法:哈希解法
1. 将数组中的元素按照字符排好序,异位词对应的排好序的字符串是相同的
2. 将排好序的字符串作为hashMap的key,value为多个异位词的list;
3. hashMap中已经是规整好的字母异位词,输出hashMap的values即可;
public List<List<String>> groupAnagrams(String[] strs) {
if(strs == null || strs.length <= 0){
return new ArrayList<>();
}
//String用来记录集合中单个元素的字母排序后的结果
HashMap<String, List<String>> map = new HashMap();
for(String str : strs){
//字符串转为字符数组并排序
char[] chars = str.toCharArray();
Arrays.sort(chars);
String key = String.valueOf(chars);
//判断map中有没有这组集合
if(!map.containsKey(key)){
List<String> list = new ArrayList();
map.put(key, list);
}
map.get(key).add(str);
}
return new ArrayList<>(map.values());
}
3.3 最长连续序列
leetcode题目链接:128. 最长连续序列
给定一个未排序的整数数组
nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为
O(n)
的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1] 输出:9
解法一:
// 解法一:先对数组进行排序,寻找最长的连续序列
// 缺点:当数组中有重复的元素时,处理方式不太雅观
public int longestConsecutive(int[] nums) {
if(nums == null){
return 0;
}
if(nums.length <= 1){
return nums.length;
}
Arrays.sort(nums);
int startIndex = 0;
int endIndex = 0;
int res = 1;
for(int i = 0; i < nums.length - 1; i++){
//处理特殊情况,数组中有元素是重复的,导致连续序列中断
if(nums[i] == nums[i+1]){
startIndex = startIndex +1;
continue;
}
//寻找连续序列
if(nums[i] + 1 == nums[i+1]){
endIndex = i + 1;
continue;
}
//数组遍历未完成,连续序列中断,计算当前最大长度
int currRes = i - startIndex + 1;
res = Math.max(res, currRes);
//从中断位置,继续出发寻找连续序列
startIndex = i+1;
}
//数组遍历完成,连续序列中断,计算当前最大长度
int currRes = nums.length - startIndex;
res = Math.max(res, currRes);
return res;
}
解法二:
// 解法二:先对数组进行排序,寻找最长的连续序列
// 相对解法一的优化:不再使用数组的下标计算连续序列的长度,而是使用计数的方式
// 这样可以避免当数组中有重复的元素时,处理方式不雅观的问题
public int longestConsecutive(int[] nums) {
if(nums == null){
return 0;
}
if(nums.length <= 1){
return nums.length;
}
Arrays.sort(nums);
int result = 1;
int count = 1;
for(int i = 0; i < nums.length - 1; i++){
//处理特殊情况,数组中有元素是重复的,导致连续序列中断
if(nums[i] == nums[i+1]){
continue;
}
//寻找连续序列
if(nums[i] + 1 == nums[i+1]){
count++;
continue;
}
result = Math.max(count, result);
count = 1;
}
result = Math.max(count, result);
return result;
}
解法三:哈希解法
// 解法三:不需要先对数组进行排序,直接用hashset存储寻找前后相邻的值是否存在,同时也处理了数组中值重复的问题
public int longestConsecutive(int[] nums) {
if(nums == null){
return 0;
}
if(nums.length <= 1){
return nums.length;
}
// 先把数组中的元素存储进hashset
Set<Integer> set = new HashSet<>();
for(int num : nums){
set.add(num);
}
int count = 1;
int max = 1;
// 遍历数组,寻找有序数组的最左边界(set中不存在当前值-1,就说明当前值可以是最左边界)
for(int num : nums){
if(set.contains(num-1)){
continue;
}
while(set.contains(num + 1)){
count++;
num++;
}
max = Math.max(count, max);
count = 1;
}
return max;
}