一、哈希表理论基础
1.哈希表
哈希表(Hash Table),也叫散列表,是一种高效数据结构 ,通过哈希函数(Hash Function)来实现快速的数据查找、插入和删除操作。
- 存储原理:将键值对中的键通过哈希函数计算,得到哈希值,这个哈希值对应哈希表中的一个位置,然后将键值对存储在该位置。比如存储学生信息,以学生学号作为键,通过哈希函数计算学号,得到在哈希表中的存储位置。
- 查找过程:查找数据时,同样用哈希函数计算键的哈希值,直接定位到哈希表中对应的位置获取数据,无需遍历整个数据集合。
- 冲突处理:不同键经过哈希函数计算可能得到相同哈希值,即哈希冲突。常见处理方法有拉链法(将冲突元素以链表形式存储在同一位置)、线性探测法(寻找下一个空闲位置存储冲突元素) 等。
2.常见的三种哈希结构
-
数组
-
set (集合)
-
map(映射)
二、有效的字母异位词
1.题目描述
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1: 输入: s = "anagram", t = "nagaram" 输出: true
示例 2: 输入: s = "rat", t = "car" 输出: false
2.思路
- 创建一个长度为 26 的整数数组
record
,用于统计字符出现的频次差异。 - 通过两个
for
循环,第一个循环遍历字符串s
,以字符与'a'
的相对数值作为索引,在record
数组中对相应位置元素做自增操作,以此统计s
中各字符出现次数;第二个循环遍历字符串t
,同样以相对数值为索引在record
数组中对相应元素做自减操作,减去t
中各字符出现次数。 - 再用
for-each
循环遍历record
数组,若有元素不为 0,则表明两个字符串所含字符的频次不一致,不是字母异位词,返回false
;若所有元素都为 0,则说明两个字符串是字母异位词,返回true
。
3.代码
class Solution {
public boolean isAnagram(String s, String t) {
int[] record = new int[26];
for (int i = 0; i < s.length(); i++) {
record[s.charAt(i) - 'a']++;
}
for (int i = 0; i < t.length(); i++) {
record[t.charAt(i) - 'a']--;
}
for (int count: record) {
if (count != 0) {
return false;
}
}
return true;
}
}
但是我自己做时候用的是普通for循环判断是否为0,没有重新定义count:
class Solution {
public boolean isAnagram(String s, String t) {
int[] sum = new int[26];
for (int i = 0; i <s.length(); i++) {
sum[s.charAt(i) -'a']++;
}
for (int i = 0; i <t.length(); i++) {
sum[t.charAt(i) -'a']--;
}
for (int i = 0; i < sum.length; i++) {
if(sum[i]!=0){
return false;
}
}
return true;
}
}
三、两个数组的交集
1.题目描述
给定两个数组 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] 也是可通过的
2.思路
- 先判断输入 nums1
或
nums2` 是否为空,为空则返回空数组 - 创建两个集合
set1
和resSet
,set1
用于存储nums1
去重后的元素 - 遍历
nums1
,将元素存入set1
实现去重 - 遍历
nums2
,若元素在set1
中存在,则存入resSet
(结果去重) - 方法一:创建与
resSet
大小相同的数组,遍历集合将元素存入数组并返回;方法二:使用流(Stream)将结果集合resSet
转换为int
数组:
3.代码
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<Integer> set1 = new HashSet<>();
Set<Integer> resSet = new HashSet<>();
for (int i:nums1) {
set1.add(i);
}
for (int i:nums2) {
if(set1.contains(i)){
resSet.add(i);
}
}
/*方法一
int[] arr = new int[resSet.size()];
int j=0;
for (int i:resSet) {
arr[j++] = i;
}
return arr;*/
//方法二
return resSet.stream()
.mapToInt(Integer::intValue)
.toArray();
}
}
四、快乐数
1.题目描述
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
编写一个算法来判断一个数 n
是不是快乐数。
示例 1:
输入:n = 19 输出:true
解释:
1^2 + 9^2 = 1 + 81 = 82
8^2 + 2^2 = 64 + 4 = 68
6^2 + 8^2 = 36 + 64 = 100
1^2 + 0^2 + 0^2 = 1
示例 2:输入:n = 2 输出:false
2.官方代码思路
- 核心逻辑:利用哈希集合
record
记录计算过程中出现的数字,通过检测数字是否重复出现来判断是否进入无限循环。 - 循环条件:当
n
不等于 1 且未出现过重复数字时,持续循环。 - 记录与更新:每次循环将当前
n
存入集合,再通过getNextNumber
方法计算下一个数字(当前数字各位平方和)。 - 终止判断:若循环终止时
n
为 1,说明是快乐数(返回true
);若因数字重复终止,说明不是快乐数(返回false
)。 - 辅助方法:
getNextNumber
编写专门负责计算一个数字的各位平方和,逻辑清晰且可复用。
3.代码
当然我自己没有说再写一个方法
import java.util.HashSet;
import java.util.Set;
class Solution {
public boolean isHappy(int n) {
Set<Integer> record = new HashSet<>();// 用于记录已出现的数字,检测循环
int sum;
while(n!=1){
if (record.contains(n)) { //如果数字重复出现,说明进入循环,不是快乐数
return false;
}
record.add(n);
sum=0;
while (n>0){
int temp = n%10;
sum+=temp*temp;
n=n/10;
}
n=sum;
}
return true;
}
}
官方:
class Solution {
public boolean isHappy(int n) {
Set<Integer> record = new HashSet<>();
while (n != 1 && !record.contains(n)) {
record.add(n);
n = getNextNumber(n);
}
return n == 1;
}
private int getNextNumber(int n) {
int res = 0;
while (n > 0) {
int temp = n % 10;
res += temp * temp;
n = n / 10;
}
return res;
}
}
五、两数之和
1.题目描述
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
2.思路
为什么用Map???
需要键值对应:Map
能同时存储元素值(key
)和其索引(value
),而Set
/List
只能存值但无法直接关联索引。
高效查找:Map
的containsKey
方法可快速判断差值是否存在,时间复杂度 O (1),比遍历集合(O (n))高效得多。
避免重复使用:通过索引区分同一值的不同位置,确保找到两个不同元素。
实现步骤:
- 用哈希表
map
存储数组元素值与索引的映射 - 遍历数组,对每个元素计算目标差值
temp = target - nums[i]
- 若哈希表中存在
temp
,则找到两个目标元素,返回当前索引i
和temp
对应的索引 - 若不存在,则将当前元素和索引存入哈希表
- 遍历结束后返回结果数组
3.代码
我觉得这个方法最容易理解
import java.util.HashMap;
import java.util.Map;
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i <nums.length; i++) {
int temp = target-nums[i];
if(map.containsKey(temp)){
res[0] = i;
res[1] = map.get(temp);
}
map.put(nums[i],i);
}
return res;
}
}
方法二
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> indexMap = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int balance = target - nums[i]; // 记录当前的目标值的余数
if(indexMap.containsKey(balance)){ // 查找当前的map中是否有满足要求的值
return new int []{i, indexMap.get(balance)}; // 如果有,返回目标值
} else{
indexMap.put(nums[i], i); // 如果没有,把访问过的元素和下标加入map中
}
}
return null;
}
双指针方法(但是我学习时候没有看这个方法希望二刷时候有时间写一下)
//使用双指针
public int[] twoSum(int[] nums, int target) {
int m=0,n=0,k,board=0;
int[] res=new int[2];
int[] tmp1=new int[nums.length];
//备份原本下标的nums数组
System.arraycopy(nums,0,tmp1,0,nums.length);
//将nums排序
Arrays.sort(nums);
//双指针
for(int i=0,j=nums.length-1;i<j;){
if(nums[i]+nums[j]<target)
i++;
else if(nums[i]+nums[j]>target)
j--;
else if(nums[i]+nums[j]==target){
m=i;
n=j;
break;
}
}
//找到nums[m]在tmp1数组中的下标
for(k=0;k<nums.length;k++){
if(tmp1[k]==nums[m]){
res[0]=k;
break;
}
}
//找到nums[n]在tmp1数组中的下标
for(int i=0;i<nums.length;i++){
if(tmp1[i]==nums[n]&&i!=k)
res[1]=i;
}
return res;
}
六、心得(真实)
学习哈希表,个人感觉比链表好理解很多,可能是原理来接触过,所以今天用时不到三个小时完成了今日打卡,确实觉得没有接触算法解题方法时候,只会使用暴力解法比如俩个for循环解答题目,原来就觉得哇塞终于解答出来了,但其实这些不叫算法刷题了,所以很开心呢跟着训练营学习,加油!!!