目录
朋友们好,今天带来的是哈希表有关的几个经典算法思维~~
哈希表
基础概念
一般哈希表都是用来快速判断一个元素是否出现集合里
总结一下,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!
哈希碰撞
哈希碰撞是指两个不同的输入通过同一个哈希函数计算得到了相同的哈希值。如图所示,小王和小李都映射到索引下标 1 的位置,这种现象就是哈希碰撞。
哈希碰撞一般有两种解决方法,即拉链法和线性探测法。
拉链法
将发生冲突的元素储存在链表中。
拉链法要选择适当的哈希表大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间
线性探测法
线性探测法需要保证 tableSize 大于 dataSize,依靠哈希表中的空位来解决碰撞问题。
day6 字母异位词
思路
由于 26 个字母的 ASCII 码是连续的,可以通过减’a’将 26 个字母映射为一个数组
其实就是判断元素是否出现过,直接hash拿下;不过这题元素少,可以用数组
注意点
length()针对字符串
length 针对数组
size() 针对map
s.toCharArray()[i]和t.charAt(i)都可以
注意:这里如果要用tochararray就要先放在外面变了之后再直接调用array,不然在循环中反复变整个值会超时间
遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了
public boolean isAnagram(String s, String t) {
//先来一层筛选
if(s.length() != t.length()){
return false;
}
//量少用数组
int[] record = new int[26];
for (int i = 0; i < s.length(); i++) {
record[s.toCharArray()[i] - 'a']++;
}
for (int i = 0; i < t.length(); i++) {
record[t.charAt(i) - 'a']--;
}
//看看数组是否为空
for (int i = 0; i < record.length; i++) {//数组的length没有括号
if (record[i] != 0){
return false;
}
}
return true;
时间复杂度:O(n)O,其中 n 为 s的长度。
空间复杂度:O(S),其中 S 为字符集大小,此处 S=26
day6 两个数组的交集 349
审题:交集 用hashSet 去重 数字量大
核心思路
第一个数组的值存入set, 第二个数组的值和set元素比对是否contain,如果contain,用另一个set记录相同的值,最后转成数组即可
注意点
add() contains()
set题目遍历记得用foreach做,因为set不需要知道顺序,存入值即可
set转数组别不会!!
public int[] intersection(int[] nums1, int[] nums2) {
//交集hash表,一个存,一个查看是否有,有的话就放数组里输出
Set<Integer> set1 = new HashSet<Integer>();
Set<Integer> set2 = new HashSet<Integer>();
for (int i = 0; i < nums1.length; i++) {//存
set1.add(nums1[i]);
}
for (int i = 0; i < nums2.length; i++) {//查+存
if (set1.contains(nums2[i])) {
//不能直接放数组里,得要先放到set里自动去重
set2.add(nums2[i]);
}
}
//转数组
int[] array = new int[set2.size()];
int j = 0;
for (int i : set2) {
array[j] = i;
j++;
}
return array;
//return set2.stream().mapToInt(x -> x).toArray();
}
方法二
由于看到有这个大小的提示,所以第一层可以用hash数组做,对应的地方变成1,然后第二个数组查找的时候如果位置上是1,那就是重复元素提出来----类似字母异味词
public int[] intersection(int[] nums1, int[] nums2) {
if(nums1 == null || nums2 == null){
return new int[0];//按照返回值
}
//用hash数组做,也只是第一次存放用hash数组,最后的还是set
int[] hash1 = new int[1005];
Set<Integer> resSet = new HashSet<>();
//这是hash数组,对应的地方+1 然后
for (int i : nums1) {
hash1[i] = 1;
}
for (int i : nums2){
if (hash1[i] == 1){
resSet.add(i);
}
}
int j = 0;
int[] result = new int[resSet.size()];
for (int i : resSet){
result[j++] = i;
}
return result;
拓展
思路
数组相交变体--重复也要输出,且跟次数少的一致
因为重复,所以用list不用set
关键不同部分:要把匹配过这个值的删掉才能刷新计数:list1.remove(Integer.valueOf(num))
public int[] intersect(int[] nums1, int[] nums2) {
//交集变体--重复也要输出,且跟次数少的一致
//因为重复,所以用list不用set
List<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();
for (int num : nums1) {
list1.add(num);
}
for (int num : nums2) {
if (list1.contains(num)) {
list2.add(num);
//关键不同部分:要把匹配过这个值的删掉才能刷新计数
//remove 有多个重载方法,有没有 Integer.valueOf 就是 remove(int index) 和 remove(Object obj) 的关系
list1.remove(Integer.valueOf(num));
}
}
//转数组
int[] array = new int[list2.size()];
int j = 0;
for (int i : list2) {
array[j++] = i;
}
return array;
}
解法一具体:
import java.util.*;
public class Solution {
public int[] intersectSorted(int[] nums1, int[] nums2) {
Arrays.sort(nums1); // 如果未排序,则先排序
Arrays.sort(nums2); // 如果未排序,则先排序
int i = 0, j = 0;
List<Integer> intersection = new ArrayList<>();
while (i < nums1.length && j < nums2.length) {
if (nums1[i] < nums2[j]) {
i++;
} else if (nums1[i] > nums2[j]) {
j++;
} else {
intersection.add(nums1[i]);
i++;
j++;
}
}
int[] result = new int[intersection.size()];
for (int k = 0; k < result.length; k++) {
result[k] = intersection.get(k);
}
return result;
}
}
day6 快乐数 202
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
tip:无限循环,即出现相同结果在集合里时,false
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。
思路
- n!=1或集合中不contains就循环
- 把一个数每位平方算法:1、%10得最后一位 2、num/10 3、result加上
- 一句话: 如果不是输出1或者set里面出现重复的数时,要往set中不断加数
public boolean isHappy(int n) {
//换种思路,如果在set中出现过就不是快乐数,如果出现1,就是
Set<Integer> set = new HashSet<Integer>();
while(n != 1 && !set.contains(n)){//排除上面2种情况要加数
set.add(n);
n = getNextNum(n);
}
return n == 1;//这个如果包含直接输出false,包含了两种结果
}
int getNextNum(int num){
int result = 0;
while (num > 0) {
int temp = num % 10;
num = num / 10;
result += temp * temp;
}
return result;
}
day6 两数之和
思路
遍历数组,每遍历一个数时就去map里找有没有加一起是target的,如果有,将这两个数的下标输出,如果没有,将这个遍历过的数和他的下表存入map中
通过key找value 所以key必须存值、value存下标
本题其实有四个重点:
- 为什么会想到用哈希表
- 哈希表为什么用map
- 本题map是用来存什么的
- map中的key和value用来存什么的
解答
- 要判断一个元素是否在集合里就要用哈希法了
- map可以存key和value,能通过一个找另外一个,而这题,既要判断值又要得到下标
- map用于存放遍历过的数组
- key存值,value存下标
map 插入用put
查找key用containsKey
通过key得value用map.get(key)
public int[] twoSum(int[] nums, int target) {
//双循环往map上想,可以减少n复杂度
//是否存在+返回下标-->map
//key 元素 value 下标
//在map中寻找是否有一致的值,没有就放入
int[] array = new int[2];
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++){
//不能先放进去,要先判断组不成target才能放进去
int temp = target - nums[i];//这是要到map里找的数
if (map.containsKey(temp)) {//别混了,是contain temp
// array[0] = i;
// array[1] = map.get(temp);//得到temp下标
// break;//这里最好加个break
return new int[]{i, map.get(temp)};//两种输出方式都可以
}
map.put(nums[i], i);
}
return array;
}
大家记住一个结论:几乎所有的求和问题,都可以转化为求差问题。 这道题就是一个典型的例子,通过把求和问题转化为求差问题,事情会变得更加简单。