哈希表(hash table)也被称为散列表,可以根据关键字快速返回相应的值,遍历时间复杂度仅仅为O(1),十分神奇。
哈希函数:其原理主要是通过哈希函数对输入值进行均匀散射处理,得到唯一的输出值,那么输入值被称为键,而该得到的输出值就被称为值。展开来说,哈希函数对输入的值例如“小明”进行映射,将其转为固定长度数组的下标,那么通过该下标我们就可以快速判断“小明”是否存在。
哈希碰撞:当输入值过多,数组长度不够,不同的输入值映射到同一个地方怎么办?有两种解决方法。一是拉链法,通过在数组元素后面添加链表进行储存。二是线性探测法,在哈希表容量大于输入元素数量情况下,当映射的空位有存放时,将其存放在下一个空位。
常见的哈希表类型:
1. 数组:数组也是一种哈希表,可以通过其下标快速查找元素。下标相当于key,里边的元素相当于value。
2. set:是一种只储存值的哈希表,类似于集合。
3. map:是一种可以储存键值对的哈希表,通过键可以快速访问值,是一种映射。
import java.util.HashSet;
public class HashSetExample {
public static void main(String[] args) {
HashSet set = new HashSet<>();
// 添加元素
set.add("apple");
set.add("banana");
set.add("orange");
set.add("apple"); // 重复元素
// 删除元素
set.remove("banana");
// 遍历元素
for (String fruit : set) {
System.out.println(fruit);
}
// 判断元素是否存在
boolean containsApple = set.contains("apple");
System.out.println("Contains apple: " + containsApple);
}
}
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
HashMap map = new HashMap<>();
// 添加键值对
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(1, "uno"); // 覆盖已存在的键
// 删除键值对
map.remove(2);
// 获取值
String value = map.get(3);
System.out.println("Value for key 3: " + value);
// 遍历键值对
for (Integer key : map.keySet()) {
String val = map.get(key);
System.out.println(key + " -> " + val);
}
// 判断键是否存在
boolean containsKey = map.containsKey(1);
System.out.println("Contains key 1: " + containsKey);
}
}
使用hashset:
1. 元素需要去重,因为hashset只能储存不一样的元素,会自动去除重复的元素。
2. 关心元素是否出现,因为hashset的查找元素很方便,只需要O(1)的时间。
使用hashmap:
1. 储存一对数据时,用hashmap比较方便。
2. 需要进行一对数据的快速查找。
给定两个字符串
s
和t
,编写一个函数来判断t
是否是s
的字母异位词。注意:若
s
和t
中每个字符出现的次数都相同,则称s
和t
互为字母异位词。
s
和t
仅包含小写字母
示例 1:
输入: s = "anagram", t = "nagaram" 输出: true示例 2:
输入: s = "rat", t = "car" 输出: false
思路:
很容易可以想到字符本身和该字符出现的次数是一个键值对,字符——键,字符出现的次数——值,本题应该用hashmap来解。
那么如何表示字符呢?char本质是数字,而字符仅包含小写字母,因此可以用数字表示。键和值都是数字,自然考虑数组。
用 字符-'a' 表示字符的键,用数组的下标表示,出现的次数用对应数组元素表示。
class Solution {
public boolean isAnagram(String s, String t) {
int [] load = new int [26];
for(int i = 0; i < s.length(); i++){
load[s.charAt(i) - 'a']++;
}
for(int i = 0; i < t.length(); i++){
load[t.charAt(i) - 'a']--;
}
for(int num : load){
if(num != 0){
return false;
}
}
return true;
}
}
给定两个数组
nums1
和nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2]示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出:[9,4] 解释:[4,9] 也是可通过的
思路:
交集其实就是该元素有没有存在在两个数组中。
可以将其中一个数组转为hashset,然后用第二个数组遍历进行contain比较,将true的结果返回到一个新数组中。
由于其大小不知道可以用动态数组储存,并使用增强for循环遍历取出。
import java.util.HashSet;
import java.util.Set;
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 set1 = new HashSet<>();
Set resSet = new HashSet<>();
//遍历数组1
for (int i : nums1) {
set1.add(i);
}
//遍历数组2的过程中判断哈希表中是否存在该元素
for (int i : nums2) {
if (set1.contains(i)) {
resSet.add(i);
}
}
//方法1:将结果集合转为数组
return resSet.stream().mapToInt(x -> x).toArray();
//方法2:另外申请一个数组存放setRes中的元素,最后返回数组
int[] arr = new int[resSet.size()];
int j = 0;
for(int i : resSet){
arr[j++] = i;
}
return arr;
}
}
编写一个算法来判断一个数
n
是不是快乐数。「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果
n
是 快乐数 就返回true
;不是,则返回false
。
输入:n = 19 输出:true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
思路:
这题就是模拟平方和的过程,但其可能会无限循环,那么如何判断新生成的数有没有出现过呢?
自然想到得hashset用保存其出现过的数,看看其会不会无限循环。
class Solution {
public boolean isHappy(int n) {
int number = n;
int sum = 0;
Set record = new HashSet<>();
while(n != 1){
while(n >= 10){
sum += (n % 10) * (n % 10);
n = n/10;
}
sum += n*n;
n = sum;
sum = 0;
if(record.contains(n)){
return false;
}
record.add(n);
}
return true;
}
}
给定一个整数数组
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]
思路:
该题可以两层for暴力破解。
由于查询的是数组的下标,因此可以考虑将数组的key-value反转。反转之后不仅可以去重,并且可以快速判断是否存在某个数。
用for遍历第二个数组,target - num[i] == 某个数,那么该数就是确定的,可以被快速判断出来。
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if(nums == null || nums.length == 0){
return res;
}
Map map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int temp = target - nums[i]; // 遍历当前元素,并在map中寻找是否有匹配的key
if(map.containsKey(temp)){
res[1] = i;
res[0] = map.get(temp);
break;
}
map.put(nums[i], i); // 如果没找到匹配对,就把访问过的元素和下标加入到map中
}
return res;
}