哈希表
当我们想使⽤哈希法来解决问题的时候,我们⼀般会选择如下三种数据结构。
- 数组
- set (集合)
- map(映射)
当我们遇到了要快速判断⼀个元素是否出现集合⾥的时候,就要考虑哈希法。如果在做⾯试题⽬的时候遇到需要判断⼀个元素是否出现过的场景也应该第⼀时间想到哈希法!
这一部分的题就是需要注意,如果用到了set,map,在定义的时候需要用包装类,也就是引用数据类型,而不是基本数据类型
0、泛型不能直接使用基本数据类型!!!
例如 int
、double
、char
等。但是 Java 提供了对应的包装类来解决这个问题,例如 Integer
、Double
、Character
等。所以,你可以在泛型中使用这些包装类来代替基本数据类型。
例如,你可以使用 Integer
代替 int
,Double
代替 double
,Character
代替 char
,以及其他包装类来替代其他基本数据类型。
// byte 对应 Byte
// short 对应 Short
// int 对应 Integer
// long 对应 Long
// float 对应 Float
// double 对应 Double
// char 对应 Character
// boolean 对应 Boolean
1、有效的字⺟异位词(数组哈希表)
242. 有效的字母异位词 - 力扣(LeetCode)
其实就是不能出现一个字母只在前面出现,但是没在后面那个字符串中。并且需要 s
和 t
中每个字符出现的次数都相同
可以用数组,遍历字符串,然后设一个数组,hash[26],每遍历到一个字母,相应位置加1,之后遍历第二个字符串,遍历到对应字母就去减1,然后看最后这个hash中有无负值
public boolean isAnagram(String s, String t) {
int[] hash = new int[26];
for(int i = 0; i < s.length(); i++){
hash[s.charAt(i) - 'a']++; // 并不需要记住字符a的ASCII,只要求出⼀个相对数值就可以了
}
for(int i = 0; i < t.length(); i++){
hash[t.charAt(i) - 'a']--;
}
for(int i = 0; i < 26; i++){
if(hash[i] != 0)
return false;
}
return true;
}
2、两个数组的交集(set)
349. 两个数组的交集 - 力扣(LeetCode)
一个set集合用于存放第一个数组中的元素,另外一个用于在遍历第二个数组的时候,去查找如果有相同元素情况,就存放进去
之后结果数组的大小,就是基于第二个set集合的大小new出来的。并且遍历第二个set集合,将里面的元素放进最终数组里。
public int[] intersection(int[] nums1, int[] nums2) {
HashSet<Integer> set = new HashSet<Integer>();
HashSet<Integer> result = new HashSet<Integer>();
for(int i = 0; i < nums1.length; i++){
set.add(nums1[i]);
}
for(int i = 0; i < nums2.length; i++){
if(set.contains(nums2[i]))
result.add(nums2[i]);
}
int[] res = new int[result.size()];
int n = 0;
for(int num: result){
res[n++] = num;
}
return res;
}
3、两数之和(HashMap)
1. 两数之和 - 力扣(LeetCode)
这道题需要明确借助map的话,key和value分别存放什么
因为我们是需要基于数组值去查询的,所以key存放的是数组值,value存放的是对应的索引
在遍历数组的时候,只需要向map去查询是否有和⽬前遍历元素匹配的数值,如果有,就找到的匹配对,如果没 有,就把⽬前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
public int[] twoSum(int[] nums, int target) {
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
int[] result = new int[2];
map.put(nums[0],0);
for(int i = 1; i < nums.length; i++){
if(map.containsKey(target - nums[i])){
result[0] = map.get(target - nums[i]);
result[1] = i;
return result;
}
map.put(nums[i],i);
}
return result;
}
4、四数相加
454. 四数相加 II - 力扣(LeetCode)
这题和四数之和的区别在于,这是四个数组,所以不需要考虑去重,因为四个数组是独立的,而四数之和问题是针对于一个数组。会可能结果出现重复的
本题的思路:
将四个数组两两划分到一组
- 遍历⼤A和⼤B数组,统计两个数组元素之和(key),和出现的次数(value),放到map中。
- 定义int变量count,⽤来统计 a+b+c+d = 0 出现的次数。
- 在遍历⼤C和⼤D数组,找到如果 0-(c+d) 在map中出现过的话,就⽤count把map中key对应的value也就是出现次数统计出来。注意value是多少,那说明就出现过几次四数之和是这个目标target值的情况
- 最后返回统计值 count 就可以了
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
HashMap<Integer,Integer> map = new HashMap<Integer, Integer>();
int count = 0;
int n;
for(int i = 0; i < nums1.length; i++){
for(int j = 0; j < nums2.length; j++){
if(map.get(nums1[i] + nums2[j]) == null)
n = 1;
else
n = map.get(nums1[i] + nums2[j])+1;
map.put(nums1[i] + nums2[j],n++);
}
}
for(int i = 0; i < nums3.length; i++){
for(int j = 0; j < nums4.length; j++){
int tmp = 0 - nums3[i] - nums4[j];
if(map.containsKey(tmp))
count += map.get(tmp);
}
}
return count;
}
-
这个题在写代码时候,需要注意,在填充次数的时候,由于Integer不存在0的情况,只是null
-
这是和int基本数据类型的一大区别,所以我们需要判断他是不是本身不存在,然后赋值
5、三数之和
15. 三数之和 - 力扣(LeetCode)
首先想到哈希解决,可以用两个for循环,然后判断出来是不是存在一个key = 【0-a-b】
但是这个就有一个问题,会有重复的三元组,需要去重
因此,这里选择用另外一种方法:双指针法,会更高效一点
-
由于我们需要返回值,不用返回下标,所以可以提前对数组进行排序
-
之所以排序,因为我们的思路是让 i 去遍历数组,然后left首先指向【i+1】的位置,right指向最后一个位置
-
如果nums[ i ] + nums[ left ] + nums[ right ] > 0;这时候如果排过序,可以让right–。就类似这样的操作。并且如果nums[ i ]本身就是大于0的,那就可以不用找了,因为left 和 right只会更大
-
也需要进行去重:
- 需要判断nums[ i ] == nums[ i - 1 ],因为如果之前一个元素和当前位置元素相同,那他获取到的结果,会和我们此次获取到的结果有重复情况,注意必须是判断当前元素和前一个元素,而不是和后一个元素nums[ i + 1 ]相比
- 在收获了一个三元组之后需要对于left指针部分去重:在left++之前,如果nums[ left ] ==nums[ left+1 ],就持续left++【用while判断】
- 在收获了一个三元组之后需要对于left指针部分去重:在right–之前,nums[ right ] == nums[ right-1 ],就持续right–【用while判断】
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<List<Integer>>();
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for(int i = 0; i < n; i++){
if(nums[i] > 0) // 排序后如果第一个元素已经大于零,无论如何组合都不可能凑成三元组,直接返回结果就可以了
return result;
if(i > 0 && nums[i] == nums[i-1]) // 去重a
continue;
int left = i + 1;
int right = n - 1;
while(left < right){
if(nums[i] + nums[left] + nums[right] > 0)
right--;
else if(nums[i] + nums[left] + nums[right] < 0)
left++;
else{
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[left]);
list.add(nums[right]);
result.add(list);
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重 一定要用while不是用if!!!!!!!
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--; //别忘了上面只是判断,这里才是真正的移动!!!!!!
left++;
}
}
}
return result;
}
6、四数之和
18. 四数之和 - 力扣(LeetCode)
和三数之和一样,都是在一个数组上寻找四个元素的和等于target值,并且要求结果里面不能有重复的四元组结合
基本思路:
在三数之和的思路上,外面再加上一层for循环
这里的剪枝和对nums[ k ]的去重稍微有点不一样,因为这次是和target手动输入的值作比较,不像三数之和是单纯的和零做比较
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int k = 0; k < nums.length; k++) {
// nums[i] > target 直接返回, 剪枝操作
if (nums[k] > 0 && nums[k] > target) {
return result;
}
if (k > 0 && nums[k - 1] == nums[k]) { // 对nums[i]去重
continue;
}
for (int i = k + 1; i < nums.length; i++) {
if (i > k + 1 && nums[i - 1] == nums[i]) { // 对nums[j]去重
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
// nums[k] + nums[i] + nums[left] + nums[right] > target int会溢出
long sum = (long) nums[i] + nums[k] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[k], nums[left], nums[right]));
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
left++;
right--;
}
}
}
}
return result;
}
基于代码随想录的学习,同时刷题总结哈希表的常用思路以及一些刷题笔记