算法day05 | 哈希专题part01

66 阅读4分钟

哈希基础

文章来源:代码随想录 哈希基础

  • 一般哈希表都是用来快速判断一个元素是否出现集合里。 哈希表使用哈希函数进行映射。
  • 哈希函数:通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值。
  • 如果hashCode得到的数值大于哈希表的大小了,也就是大于tableSize了,怎么办呢?
    • 此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作。
  • 一般哈希碰撞有两种解决方法, 拉链法和线性探测法。
    • 拉链法:冲突的元素使用链表储存 Screenshot 2024-06-11 at 21.48.09.png
    • 线性探测法:一定要保证tableSize大于dataSize。我们需要依靠哈希表中的空位来解决碰撞问题。冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。 Screenshot 2024-06-11 at 21.50.02.png

哈希表常见结构

  • 数组
  • set(集合)
  • map(映射)

如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!

242.有效的字母异位词

题目链接 因为是哈希表专题,我尽量避免使用暴力解法而是使用哈希表的常见结构。这道题我使用了HashMap,然后第一遍遍历第一个string,数char的个数,然后遍历第二个string,把相同的char一一减去。最后剩下如果都为0则true。

Map<Character, Integer> m = new HashMap<Character, Integer>();

for(int i=0;i < s.length();i++){
    Character c = s.charAt(i);
    if(!m.containsKey(c)){
        m.put(c, 1);
    }else{
        m.put(c, m.get(c) + 1);
    }
}

for(int i=0;i < t.length();i++){
    Character c = t.charAt(i);
    if(!m.containsKey(c)){
        return false;
    }else{
        m.put(c, m.get(c) - 1);
    }
}

for(int i : m.values()){
    if(i != 0){
        return false;
    }
}

return true;

随想录代码

随想录的逻辑差不多但是使用了更简单的数组来进行统计。

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; 

349. 两个数组的交集

题目链接

Map<Integer, Integer> m = new HashMap<Integer, Integer>();
Set<Integer> set = new HashSet<Integer>();
for(int i=0;i < nums1.length;i++){
    if(!m.containsKey(nums1[i])){
        m.put(nums1[i], 1);
    }
}

for(int i=0;i < nums2.length;i++){
    if(m.containsKey(nums2[i])){
        set.add(nums2[i]);
    }
}

int[] res = new int[set.size()];
int i = 0;

for (Iterator<Integer> it = set.iterator(); it.hasNext(); ) {
    int f = it.next();
    res[i] = f;
    i++;
}

return res;

随想录代码

随想录使用了set

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. 快乐数

题目链接 这道题的难度主要是要理解什么叫进入无限循环得不到1。题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!

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. 两数之和

题目链接

Map<Integer, Integer> m = new HashMap<Integer,Integer>();
int[] res = new int[2];
for(int i=0;i < nums.length;i++){
    if(!m.containsKey(target - nums[i])){
        m.put(nums[i], i);
    }else{
        res[0] = i;
        res[1] = m.get(target - nums[i]);
        break;
    }
}
return res;

随想录代码

在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。

  • 哈希表
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++){
    int temp = target - nums[i];   // 遍历当前元素,并在map中寻找是否有匹配的key
    if(map.containsKey(temp)){
        res[1] = i;
        res[0] = map.get(temp);
        break;
    }
    map.put(nums[i], i);    // 如果没找到匹配对,就把访问过的元素和下标加入到map中
}
return res;
  • 双指针
int m=0,n=0,k,board=0;
int[] res=new int[2];
int[] tmp1=new int[nums.length];
//备份原本下标的nums数组
System.arraycopy(nums,0,tmp1,0,nums.length);
//将nums排序
Arrays.sort(nums);
//双指针
for(int i=0,j=nums.length-1;i<j;){
    if(nums[i]+nums[j]<target)
        i++;
    else if(nums[i]+nums[j]>target)
        j--;
    else if(nums[i]+nums[j]==target){
        m=i;
        n=j;
        break;
    }
}
//找到nums[m]在tmp1数组中的下标
for(k=0;k<nums.length;k++){
    if(tmp1[k]==nums[m]){
        res[0]=k;
        break;
    }
}
//找到nums[n]在tmp1数组中的下标
for(int i=0;i<nums.length;i++){
    if(tmp1[i]==nums[n]&&i!=k)
        res[1]=i;
}
return res;