链接
https://programmercarl.com/%E5%93%88%E5%B8%8C%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html
哈希表理论基础(Hash table)
- 哈希表是根据关键码的值而直接进行访问的数据结构。
- 关键码即数组的索引下标
- 数组就是一张哈希表。
- 应用:判断一个元素是否出现在集合里。时间复杂度O(1)
哈希函数
- 通过hashCode将名字转化为数值,一般hashcode是通过特定编码方式,将其他数据格式转化为不同的数值。就将学生名字映射为哈希表上的索引数字了。
- 为了保证映射出来的索引数值都落在哈希表上,会在再次对数值做一个【取模】的操作。
哈希碰撞
- 如果学生数量>哈希表大小?避免不了会有几位学生的名字同时映射到哈希表同一个索引下标的位置。
- 两种哈希碰撞方法
- 拉链法:发生冲突的元素被存放在列表中。
- 选择适当的哈希表的大小,既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
- 线性探测法:
- 必要条件:tablesize>datasize,依靠哈希表中的空位来解决碰撞问题。冲突的位置放了小李,就向下找一个空位置放置小王的信息
- 拉链法:发生冲突的元素被存放在列表中。
常见的三种哈希结构 牺牲了空间换取了时间
- 三种
- 数组
- set(集合)
- map(映射)
242.有效的字母异位词(字母的构成相同,排列顺序不同)
题目
给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的字母异位词。
输入: s = "anagram", t = "nagaram" 输出: true
输入: s = "rat", t = "car" 输出: false
1 <= s.length, t.length <= 5 * 10<sup>4</sup>
s
和 t
仅包含小写字母
思路
-
数据结构选择
- 数组,哈希值小且范围小,用26个字母。
- set集合,哈希数值很大用set.
- map,k-value.
-
分析
- 小写字母,a-z是连续的,对应数组0-25
- 统计s的每个字母的频率,t的每个字母的频率,两者的差为0的话,说明是字母异位词。
-
代码思路
- 初始化哈希表 record
- 循环,一次+频率,一次-频率;
- 检查一下count;如果不是0出现错误,如果是0,就返回正确值
-
代码
-
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; } }
- .charAt(i)单词中第i个字母
- for(int count:record) record计数
-
349.两个数组的交集
题目
给定两个数组 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] 也是可通过的
提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
思路
-
初步思路:计算两个数组中每个数字出现的频率,然后两个频率哈希表相减,nums1减去nums2的哈希表,如果哈希表中的某个差值不等于num1的原频率,说明是交集,返回这个数
-
class Solution { public int[] intersection(int[] nums1, int[] nums2) { int[] record=new int[10]; ArrayList<Integer> result = new ArrayList<>(); for(int i=0;i<nums1.length;i++){ record[nums1[i]]++; } for(int i=0;i<nums2.length;i++){ record[nums2[i]]--; } for(int i=0;i<record.length;i++){ if (record[i]!=nums1[i]){ result.add(record[i]); } } // 将 ArrayList 转换为 int[] 数组 int[] resultArray = new int[result.size()]; for (int i = 0; i < result.size(); i++) { resultArray[i] = result.get(i); } return resultArray; } }
-
-
报错
- 修正思路
- 将nums1和nums2的数组变为哈希表,再判断如果表1和表2中的某数值的频率都大于0时,说明有交叉集,去重,放入到res数组中。
-
class Solution { public int[] intersection(int[] nums1, int[] nums2) { int[] hash1 = new int[1002]; int[] hash2 = new int[1002]; for(int i : nums1) hash1[i]++; for(int i : nums2) hash2[i]++; List<Integer> resList = new ArrayList<>(); for(int i = 0; i < 1002; i++) if(hash1[i] > 0 && hash2[i] > 0) resList.add(i); int index = 0; int res[] = new int[resList.size()]; for(int i : resList) res[index++] = i; return res; } }
-
思路
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<>(); //遍历数组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; } }
202.快乐数
题目
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
输入:n = 19 输出:true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
示例 2:
输入:n = 2 输出:false
提示:
1 <= n <= 2<sup>31</sup> - 1
思路
- sum会无限循环,重复出现(很关键)
- 如果重复了就是return false, 如果sum没有重复出现,一直找到sum为1为止。
-
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
,请你在该数组中找出 **和为目标值 **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]
提示:
2 <= nums.length <= 10<sup>4</sup>
-10<sup>9</sup> <= nums[i] <= 10<sup>9</sup>
-10<sup>9</sup> <= target <= 10<sup>9</sup>
- 只会存在一个有效答案
思路
假如target=9,找到一个3,需要查看3前后的哈希表中有没有6.
选择数据结构为map,元素key和下标value
- 用map作为查询,比如276,第一个数为2,map查询是否有相加=9的,第2个为7,所以记录7的键和值。然后遍历到第2个数,为7,查询是否有相加=9的,查到2,记录键和值。最后得出2和7的坐标
- 代码
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> map=new HashMap<>();
for(int i=0;i<nums.length;i++){//遍历当前元素,并在map中寻找是否有匹配的key
int temp=target-nums[i];//求差得到另外一个元素
if(map.containsKey(temp)){
res[1]=i;
res[0]=map.get(temp);
break;
}
map.put(nums[i],i);//如果没有找到匹配对,就把访问过的元素和下标加入到map中
}
return res;
}
}
- 注意
- map怎么用于查询的?map.containsKey(temp)
- 怎么确定mapd的查询值?求差值
- 最后保存两个元素到map中
心得
终于把打卡补上了,今天的任务也大致完成了,题目有一些多,每天需要花三四个小时,一定要坚持下来,不要再漏卡了,不然补起来很吃力