哈希表(英文名字为Hash table,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道都是指hash table就可以了)。
哈希表是根据关键码的值而直接进行访问的数据结构。
哈希表能解决什么问题呢,一般哈希表都是用来快速判断一个元素是否出现集合里。 当遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
有效的字母异位词
前提:假设字符串中只出现小写字母
要求:输入两个字符串,判断一个字符串是不是另一个字符串的字母异位词
难点:对题意的理解,它给出的示例也是真的误导我了,我以为是每两个单词交换,实际题意是两个单词出现的字母以及出现的次数相同
方法:哈希法,将26个小写字母映射到一个哈希表(这里就是个数组,因为长度是确定的),遍历目标字符串,将出现的字符在哈希表中做+1操作,遍历完成后,遍历匹配字符串,对出现的字符在哈希表中做-1操作,最终看我们的哈希表的情况,如果仍然全为0,则说明两个字符串是字母异位词,否则就不是
注意:这里哈希法使用数组这个数据结构是因为,26个小写字母其大小是固定的,所以在这里用数组较好,也就是说,当使用数组来做哈希的题目时,是因为题目都限制了数值的大小
public static boolean isAnagram(String s, String t) {
//将26个小写字母映射到大小为26的数组
int[] record = new int[26];
for (char c: s.toCharArray()) {
record[c - 'a'] += 1; //a-a =0
}
for (char c: t.toCharArray()) {
record[c - 'a'] -= 1;
}
for (int i:record) {
if (i != 0){
return false;
}
}
return true;
}
两个数组的交集
前提:找出两个数组的交集
要求:输出结果中的每一个元素一定是唯一的,可以不考虑输出结果的顺序
重点:学会使用一种哈希数据结构,unorderd_set
注意:这里同下面的题一样都是使用哈希法,但是这道题不便于用数组这种数据结果,因为对于我们将要求出的交集是一个不确定大小的,也就是说,如果哈希值比较少、特别的分散、跨度也非常大,使用数组就会造成空间的极大浪费
public static int[] intersection(int[] nums1, int[] nums2) {
if (nums1 == null || nums2 == null){
return new int[0];
}
Set<Integer> nums1Ele = new HashSet<>();//用来保存nums1中出现的不重复的元素
Set<Integer> result = new HashSet<>();//用来保存交集中的元素,就算有重复元素也不会再加进去
//遍历nums1
for (int i : nums1) {
nums1Ele.add(i);
}
//遍历nums2并判断nums1Ele中是否已经存在
for (int i : nums2) {
if (nums1Ele.contains(i)){
result.add(i);
}
}
//将结果转为数组
int[] resArr = new int[result.size()];
int index = 0;
for (int i : result) {
resArr[index++] = i;
}
return resArr;
}
快乐数
前提:输入一个正整数
要求:对输入的正整数每个位置的数求平方再相加直到和为1返回true,否则无限循环下去返回false
难点:除了用暴力解法怎么简单快速的去除正整数的各个位置上的数呢?就算是得到 1也可能会循环很多次,那么我们怎么知道它会陷入无限循环而始终得不到1呢?
关键:无限循环其中隐藏的信息就是求和的过程中sum的值会重复出现!所以我们只需要判断sum有没有重复出现,直到sum重复出现即可返回false。
public static boolean isHappy(int n) {
int sum = 0, temp = 0;
Set<Integer> sumSet = new HashSet<>();
while (sum != 1){
while (n > 0){ //一次while循环结束一次求和结束
temp = n % 10; //每次取的就是n的个位数字
sum += temp * temp;
n = n / 10;
}
//如果已经包含sum了,就直接返回false
if (sumSet.contains(sum)){
return false;
}
//否则就继续向集合中添加
sumSet.add(sum);
if (sum != 1){//如果sum!=1再继续赋值给n,继续求和
n = sum;
sum = 0;//这里要注意将sum的值初始化为0,因为将进行下一个n的求和操作了
}
}
return true;
}
两数之和
前提:只会存在一个答案,也就是说数组中只会有一种可能,还有就是一个元素只能使用一次
要求:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
本题有四个重点:
- 为什么会想到用哈希表
- 哈希表为什么用map
- 本题map是用来存什么的
- map中的key和value用来存什么的
public static int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
Map<Integer, Integer> record = new HashMap<>();
for (int i=0; i< nums.length; i++){
if (!record.containsKey(target - nums[i])){//这里就避免了一个元素使用两次的问题
record.put(nums[i], i);
System.out.println("add----"+nums[i]);
}else{//题目前提是只存在一个答案,所以当走到这一步时我们就可以返回了,不用再继续循环了
//并且也避免了如果后面还有符合的,但是我们都不管了,只需要以第一个为准就行
result[1] = i;
result[0] = record.get(target - nums[i]);
return result;
}
}
return result;
}
四数相加
前提:给定四个包含整数的数组,从每一个数组中取一个元素,四个元素相加和为0
要求:统计满足条件的组合有多少种,重点是这里不考虑重复,就变得简单的多了
关键:将四数相加变成两数相加,先将前两个数组元素求和存在一个unorder_map中,key为两元素的和,value为和为key值的出现的次数,然后思路就与下面的两数之和差不多了
方法:分组+哈希法
- 首先求出前两个数组的所有元素组合的sum1,并且以这个sum1为map的key值,如果遇到重复的sum1,则将该sum1对应的value值➕1(因为本题是求组合数)
- 然后依次遍历求出后两个数组元素对应的sum2,如果0➖sum2有对应的sum1,则sum1的vaue值就是与sum2组合数
// 四数相加,求和为0的组合数,组合是可以重复的
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer, Integer> sum = new HashMap<>();
for (int num1 : nums1){
for (int num2 : nums2){
int tempSum = num1 + num2;
// if (!sum.containsKey(tempSum)){
// sum.put(tempSum, 1);
// }else{
// sum.put(tempSum, sum.get(tempSum) + 1);
// }
// 以上if-else语句可以用一条语句实现
// getOrDefault(key, default)如果存在key, 则返回其对应的value, 否则返回给定的默认值
sum.put(tempSum, sum.getOrDefault(tempSum, 0) + 1);
}
}
int res = 0;
for (int num3 : nums3){
for (int num4 : nums4){
int tempSum = num3 + num4;
if (sum.containsKey(0 - tempSum)){
res += sum.get(0 - tempSum);
}
}
}
return res;
}
赎金信
前提:假设字符串中只包含小写字母
难点:并不是在遍历前一个字符串,再循环遍历第二个字符串,只要那个字符存在就可以,理解题意,第二个字符串中的字符是不能重复使用的,简单点说就是前面字符串所出现的字符不仅在后一个字符串中要出现,并且出现次数要大于等于在前一个字符串中的出现次数
方法:先做哈希映射,这里也要注意,选择什么数据结构,然后就是找到一个在哈希表中对应减1
public static boolean canConstruct(String ransomNote, String magazine) {
int[] record = new int[26];
int index = 0;
//映射magazine中的字符,以及记录各个字符出现的次数
for (char c : magazine.toCharArray()){
index = c - 'a';
record[index]++;
}
for (char c : ransomNote.toCharArray()){
index = c - 'a';
//这里值得注意的是,我们遍历判断这里的c是否存在与record数组中很简单,但是次数怎么解决,我们在没有遍历完之前,是不知道ransomNote中的字符情况的
if (record[index] != 0){
record[index]--;
}else{
return false;
}
}
return true;
}
三数之和
题目:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
问题:该题归类在哈希表中,在结合前面四数相加这些题,很容易想到又用哈希法来解决,但问题在于不能包含重复的三元组
难点:输出符合要求的三元组,不仅仅是输出组合的次数
方法:
- 哈希(去重的细节会比较麻烦)
- 排序+双指针(去重依然很关键,对a,b,c都得去重)
- 排序,我们才知道当和大于或者小于0的时候,指针怎么移动
public List<List<Integer>> threeSum(int[] nums) {
//将数组升序排列
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
//一层for循环,先确定a的值
for (int i = 0; i < nums.length; i++){
if (nums[i] > 0){
return res;
}
//对a也就是i进行去重,就是如果当前i对应的值等于前一个值表示重复了
if (i > 0 && nums[i] == nums[i - 1]){
continue;
}
int left = i + 1, right = nums.length - 1;
//边界问题,left == right已经就没有意义了
while(left < right){
if (nums[i] + nums[left] + nums[right] > 0){
right--;
}
else if(nums[i] + nums[left] + nums[right] < 0){
left++;
}else{
//收集结果了
res.add(Arrays.asList(nums[i], nums[left], nums[right]));
//b, c去重,把去重放在收获一个结果的后面,至少收集一个结果
while(right > left && nums[left] == nums[left + 1]){
left++;
}
while(right > left && nums[right] == nums[right - 1]){
right--;
}
right--;
left++;
}
}
}
return res;
}
四数之和
题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。
思路:延续了前一题三数之和的思路,三数之和主要用一层for循环确定a值,四数之和就用两层for循环先确定啊a和b的值
public List<List<Integer>> fourSum(int[] nums, int target) {
ArrayList<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
for(int i = 0; i < nums.length; i++){
//剪枝
if(nums[i] > target && nums[i] >0){
return res;
}
//去重
if(i > 0 && nums[i] == nums[i - 1]){
continue;
}
for(int j = i + 1; j < nums.length; j++){
if (j > i + 1 && nums[j] == nums[j - 1]){
continue;
}
int left = j + 1, right = nums.length - 1;
while(left < right){
long sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target){
right--;
}else if(sum < target){
left++;
}else{
res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
//去重
while(left < right && nums[left] == nums[left + 1]){
left++;
}
while(left < right && nums[right] == nums[right - 1]){
right--;
}
//这里还应该注意,为什么双指针移动的操作是在去重的后面,因为,我们在上面收集了当前结果的时候,
//如果下一个指针和当前指针相同才继续移动,注意注意注意
left++;
right--;
}
}
}
}
return res;
}