代码随想录算法训练营第五天|哈希表理论基础、有效的字母异位词、两个数组的交集、快乐数、两数之和

哈希表应用与算法解析

一、哈希表理论基础

1.哈希表

   哈希表(Hash Table),也叫散列表,是一种高效数据结构 ,通过哈希函数(Hash Function)来实现快速的数据查找、插入和删除操作。

  • 存储原理:将键值对中的键通过哈希函数计算,得到哈希值,这个哈希值对应哈希表中的一个位置,然后将键值对存储在该位置。比如存储学生信息,以学生学号作为键,通过哈希函数计算学号,得到在哈希表中的存储位置。
  • 查找过程:查找数据时,同样用哈希函数计算键的哈希值,直接定位到哈希表中对应的位置获取数据,无需遍历整个数据集合。
  • 冲突处理:不同键经过哈希函数计算可能得到相同哈希值,即哈希冲突。常见处理方法有拉链法(将冲突元素以链表形式存储在同一位置)、线性探测法(寻找下一个空闲位置存储冲突元素) 等。

2.常见的三种哈希结构

  • 数组

  • set (集合)

  • map(映射)

二、有效的字母异位词

1.题目描述

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例 1: 输入: s = "anagram", t = "nagaram" 输出: true

示例 2: 输入: s = "rat", t = "car" 输出: false

2.思路

  1. 创建一个长度为 26 的整数数组 record,用于统计字符出现的频次差异。
  2. 通过两个 for 循环,第一个循环遍历字符串 s,以字符与 'a' 的相对数值作为索引,在 record 数组中对相应位置元素做自增操作,以此统计 s 中各字符出现次数;第二个循环遍历字符串 t,同样以相对数值为索引在 record 数组中对相应元素做自减操作,减去 t 中各字符出现次数。
  3. 再用 for-each 循环遍历 record 数组,若有元素不为 0,则表明两个字符串所含字符的频次不一致,不是字母异位词,返回 false;若所有元素都为 0,则说明两个字符串是字母异位词,返回 true

3.代码

class Solution {
    public boolean isAnagram(String s, String t) {
        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;                       
    }
}

但是我自己做时候用的是普通for循环判断是否为0,没有重新定义count:

class Solution {
    public boolean isAnagram(String s, String t) {
        int[] sum = new int[26];
        for (int i = 0; i <s.length(); i++) {
            sum[s.charAt(i) -'a']++;
        }
        for (int i = 0; i <t.length(); i++) {
            sum[t.charAt(i) -'a']--;

        }
        for (int i = 0; i < sum.length; i++) {
            if(sum[i]!=0){
                return false;
            }

        }
        return true;


        }
    }

三、两个数组的交集

1.题目描述

给定两个数组 nums1 和 nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

2.思路

  1. 先判断输入 nums1nums2` 是否为空,为空则返回空数组
  2. 创建两个集合 set1 和 resSetset1 用于存储 nums1 去重后的元素
  3. 遍历 nums1,将元素存入 set1 实现去重
  4. 遍历 nums2,若元素在 set1 中存在,则存入 resSet(结果去重)
  5. 方法一:创建与 resSet 大小相同的数组,遍历集合将元素存入数组并返回方法二:使用流(Stream)将结果集合 resSet 转换为 int 数组:

3.代码

import java.util.HashSet;
import java.util.Set;

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        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<>();

        for (int i:nums1) {
            set1.add(i);
        }
        for (int i:nums2) {
            if(set1.contains(i)){
                resSet.add(i);
            }
        }
        /*方法一
        int[] arr = new int[resSet.size()];
        int j=0;
        for (int i:resSet) {
            arr[j++] = i;
        }
        return arr;*/
        //方法二
        return resSet.stream()
                .mapToInt(Integer::intValue)
                .toArray();
    }
}

四、快乐数

1.题目描述

「快乐数」 定义为:

  对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

编写一个算法来判断一个数 n 是不是快乐数。

示例 1:

输入:n = 19  输出:true
解释:
1^2 + 9^2 = 1 + 81 = 82  8^2 + 2^2 = 64 + 4 = 68
6^2 + 8^2 = 36 + 64 = 100   1^2 + 0^2 + 0^2 = 1

示例 2:输入:n = 2  输出:false

2.官方代码思路

  1. 核心逻辑:利用哈希集合 record 记录计算过程中出现的数字,通过检测数字是否重复出现来判断是否进入无限循环。
  2. 循环条件:当 n 不等于 1 且未出现过重复数字时,持续循环。
  3. 记录与更新:每次循环将当前 n 存入集合,再通过 getNextNumber 方法计算下一个数字(当前数字各位平方和)。
  4. 终止判断:若循环终止时 n 为 1,说明是快乐数(返回 true);若因数字重复终止,说明不是快乐数(返回 false)。
  5. 辅助方法getNextNumber 编写专门负责计算一个数字的各位平方和,逻辑清晰且可复用。

3.代码

当然我自己没有说再写一个方法

import java.util.HashSet;
import java.util.Set;

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> record = new HashSet<>();// 用于记录已出现的数字,检测循环
        int sum;
        while(n!=1){
            if (record.contains(n)) {  //如果数字重复出现,说明进入循环,不是快乐数
                return false;
            }
            record.add(n);
            sum=0;
            while (n>0){
                int temp = n%10;
                sum+=temp*temp;
                n=n/10;
            }
            n=sum;
        }
        return true;

    }
}

官方:

class Solution {
    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.题目描述

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

2.思路

为什么用Map???

需要键值对应Map能同时存储元素值(key)和其索引(value),而Set/List只能存值但无法直接关联索引。

高效查找MapcontainsKey方法可快速判断差值是否存在,时间复杂度 O (1),比遍历集合(O (n))高效得多。

避免重复使用:通过索引区分同一值的不同位置,确保找到两个不同元素。

实现步骤:

  1. 用哈希表 map 存储数组元素值与索引的映射
  2. 遍历数组,对每个元素计算目标差值 temp = target - nums[i]
  3. 若哈希表中存在 temp,则找到两个目标元素,返回当前索引 i 和 temp 对应的索引
  4. 若不存在,则将当前元素和索引存入哈希表
  5. 遍历结束后返回结果数组

3.代码

我觉得这个方法最容易理解

import java.util.HashMap;
import java.util.Map;

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[2];
        Map<Integer,Integer> map = new HashMap<>();
        for (int i = 0; i <nums.length; i++) {
            int temp = target-nums[i];
            if(map.containsKey(temp)){
                res[0] = i;
                res[1] = map.get(temp);
            }
            map.put(nums[i],i);

        }
        return res;



    }
}

方法二

public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> indexMap = new HashMap<>();
    
    for(int i = 0; i < nums.length; i++){
        int balance = target - nums[i];  // 记录当前的目标值的余数
        if(indexMap.containsKey(balance)){  // 查找当前的map中是否有满足要求的值
            return new int []{i, indexMap.get(balance)}; //  如果有,返回目标值
        } else{
            indexMap.put(nums[i], i); //  如果没有,把访问过的元素和下标加入map中
        }
    }
    return null;
}

双指针方法(但是我学习时候没有看这个方法希望二刷时候有时间写一下)

//使用双指针
public int[] twoSum(int[] nums, int target) {
    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;
}

六、心得(真实)

   学习哈希表,个人感觉比链表好理解很多,可能是原理来接触过,所以今天用时不到三个小时完成了今日打卡,确实觉得没有接触算法解题方法时候,只会使用暴力解法比如俩个for循环解答题目,原来就觉得哇塞终于解答出来了,但其实这些不叫算法刷题了,所以很开心呢跟着训练营学习,加油!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值