使用哈希表的好处
- 哈希表可以帮助我们快速存取和提取数据。
哈希表的原理
-
设置一个哈希函数,当我们要存取数据的时候,通过哈希函数来找到对应的映射关系。
-
就比如说:现在我要存储4个元素 13 7 14 11,显然,我们可以用数组来存。也就是:a[1] = 13; a[2] = 7; a[3] = 14; a[4] = 11;当然,我们也可以用Hash来存。下面给出一个简单的Hash存储:
- 先来确定那个函数。我们就用h(ki) = ki%5;(这个函数不用纠结,我们现在的目的是了解为什么要有这么一个函数)。
- 对于第一个元素 h(13) = 13%5 = 3; 也就是说13的下标为3;即Hash[3] = 13;
- 对于第二个元素 h(7) = 7 % 5 = 2; 也就是说7的下标为2; 即Hash[2] = 7;
- 同理,Hash[4] = 14; Hash[1] = 11;
- 现在我要你查找11这个元素是否存在。你会怎么做呢?当然,对于数组来说,那是相当的简单,一个for循环就可以了。也就是说我们要找4次。
- 下面我们来用Hash找一下。
首先,我们将要找的元素11代入刚才的函数中来映射出它所在的地址单元。也就是h(11) = 11%5 = 1 了。下面我们来比较一下Hash[1]=11, 这个问题就很简单了。也就是说我们就找了1次。这个就是Hash的妙处了。
-
由此,我们发现,使用哈希表可以节省时间,提高程序运行的速率,这个再刷oj的时候就很重要了
例题1
大餐 是指 恰好包含两道不同餐品 的一餐,其美味程度之和等于 2 的幂。
你可以搭配 任意 两道餐品做一顿大餐。
给你一个整数数组 deliciousness ,其中 deliciousness[i] 是第 i 道餐品的美味程度,返回你可以用数组中的餐品做出的不同 大餐 的数量。结果需要对 109 + 7 取余。
注意,只要餐品下标不同,就可以认为是不同的餐品,即便它们的美味程度相同。
示例 1:
输入:deliciousness = [1,3,5,7,9]
输出:4
解释:大餐的美味程度组合为 (1,3) 、(1,7) 、(3,5) 和 (7,9) 。
它们各自的美味程度之和分别为 4 、8 、8 和 16 ,都是 2 的幂。
示例 2:
输入:deliciousness = [1,1,1,3,3,3,7]
输出:15
解释:大餐的美味程度组合为 3 种 (1,1) ,9 种 (1,3) ,和 3 种 (1,7) 。
提示:
1 <= deliciousness.length <= 105
0 <= deliciousness[i] <= 220
-
分析:最开始我的想法就是:每次在数组找两个数,然后累加,最后判断这个结果是不是2的n次幂,就可以得到结果。
-
public static int countPairs(int[] deliciousness) { int res=0; for (int i = 0; i < deliciousness.length; i++) { for(int j=i+1;j<deliciousness.length;j++){ int a=deliciousness[i]+deliciousness[j]; if (check(a)){ res++; } } } return res; } //判断一个数是不是2的几次幂 public static boolean check(int num){ return num > 0 && (num & (num - 1)) == 0; }}
-
但是遗憾这个方法思路没有错,但是却超时了。
-
使用哈希表分析:就是用这个数组(比如是1,1,1,3,3,3,7)能得到的最大的数(7)的两倍(14)作为一个范围(1-14),在这个范围内能得到的2的次幂的有多少个,并且这些数具体是多少(1,4,8),用这些数减去我们遍历数组得到的每一个数(num),这个数(1/4/8-num)在原来数组里面有多少个,就有多少个答案,加起来就可以了。
import java.util.*;
public class Main {
public static void main(String[] args){
int[] deliciousness = {1,1,1,3,3,3,7};
System.out.println(countPairs(deliciousness));
}
public static int countPairs(int[] deliciousness) {
final int MOD = 1000000007;//结果需要的取余
int maxVal = 0;
for (int val : deliciousness) {
maxVal = Math.max(maxVal, val);//找到数组里面最大的一个数
}
int maxSum = maxVal * 2;//找到最大的范围,一直循环要最接近最大的范围
int pairs = 0;//结果返回
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
int n = deliciousness.length;//长度
for (int i = 0; i < n; i++) {
int val = deliciousness[i];
for (int sum = 1; sum <= maxSum; sum <<= 1) {//算数左移,1,10,100,1000,10000
int count = map.getOrDefault(sum - val, 0);//
pairs = (pairs + count) % MOD;
}
map.put(val, map.getOrDefault(val, 0) + 1);
}
return pairs;
}
}
例题2:和相同的二元子数组
给你一个二元数组 nums ,和一个整数 goal ,请你统计并返回有多少个和为 goal 的 非空 子数组。子数组 是数组的一段连续部分。
示例 1:
输入:nums = [1,0,1,0,1], goal = 2
输出:4
解释:
如下面黑体所示,有 4 个满足题目要求的子数组:
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
示例 2:
输入:nums = [0,0,0,0,0], goal = 0
输出:15
- 分析:一开始是使用数组方法,遍历数组,看哪两个数相减或者是他本身符合goal的要求,就可以直接++起来,很遗憾也是超时的
-
public static int numSubarraysWithSum(int[] nums, int goal) { int res=0; List<Integer> list=new ArrayList<>();//用来记录阶段的和 int sum=0;//用来统计数字的和 for (int i = 0; i < nums.length; i++) { sum+=nums[i]; //从头开始的阶段和的子数组也符合要求 if (sum==goal) res++; //阶段性的组合也符合要求 for (int j = 0; j < list.size(); j++) { if (sum-list.get(j)==goal) res++; } list.add(sum);//每一次都要添加数据到上面 } return res; }}
- 用哈希表分析:我们用哈希表记录每一种前缀和出现的次数,假设我们当前枚举到元素 nums[j],我们只需要查询哈希表中元素sum[j]−goal 的数量即可,这些元素的数量即对应了以当前 j 值为右边界的满足条件的子数组的数量。最后这些元素的总数量即为所有和为 goal 的子数组数量。在实际代码中,我们实时地更新哈希表,以防止出现 i≥j 的情况。
class Solution {
public int numSubarraysWithSum(int[] nums, int goal) {
int sum = 0;
Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();
int ret = 0;
for (int num : nums) {
cnt.put(sum, cnt.getOrDefault(sum, 0) + 1);
sum += num;
ret += cnt.getOrDefault(sum - goal, 0);
}
return ret;
}
}