代码随想录算法营 day5-哈希表-LC242&349&202&1.两数之和

基础知识:

哈希是什么: 快速!找出集合里的元素。时间复杂度 O(1)

哈希函数 hash function: 把其他数值格式转化成 index 也就是hashCode

hashCode: 是通过特定编码方式,可以将其他数据格式转化为不同的数值 (🌰 将学生名字映射在哈希表的index上,用来找学生属于哪个学校)但是如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?

hashCode 的index 大于 tableSize:此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作。

哈希碰撞:两个数值都映射到了hash表的同一个index下。---解决办法:拉链法线性探测法。

拉链法:发生冲突的元素都被存储在链表中。就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间

线性探测法:一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。

常见3种哈希结构: 数组,set(集合),map (映射)

 - 我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset

- map中,在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现

什么是 流 “Stream”:在Java中,Stream API 是Java 8中引入的一个重要特性,它提供了一种高效且简洁的方法来处理数据序列。流可以看作是数据项的序列,并提供了许多操作以对这些数据进行计算。流API借鉴了函数式编程的思想,允许以声明式的方式处理数据,即表达做什么,而不是怎么做。

流操作通常会形成一个管道,其中包含一系列的中间操作(如筛选、映射),以及一个终端操作(如收集、求和)来产生一个结果或副作用。

  • 无状态: 流操作应该是无状态的,避免使用外部变量。
  • 单次使用: 流只能被消费一次。一旦一个终端操作被执行,流就会关闭。

➡️流的创建:

  • 从集合创建: 可以从集合(如List、Set)通过 stream() 方法创建流。
  • 从数组创建: 通过 Arrays.stream(T[] array) 方法。
  • 静态工厂方法: 如 Stream.of(T...)IntStream.range(int startInclusive, int endExclusive)

➡️流的操作:

  • 中间操作: 中间操作返回流本身,并允许多个操作链接在一起。常见的中间操作包括 filter(过滤)、map(映射)、sorted(排序)等。
  • 终端操作: 终端操作返回一个结果或产生一个副作用。常见的终端操作包括 forEach(遍历)、collect(收集到集合)、reduce(归约)、sum(求和)等。

➡️并行流:

  • 并行处理: 流可以非常简单地转换为并行模式,以利用多核处理器的优势,仅需调用 parallel() 方法。
  • 注意事项: 并行流使用共享的 ForkJoinPool,并且并不总是比顺序流快,特别是在数据量较小或计算不密集时

242.有效的字母异位词

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

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

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

说明: 你可以假设字符串只包含小写字母。

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']++; //遍历s字符串,统计字符出现次数在哈希表上以递增记录
    }
    for(int i =0; i <t.length(); i++){
        record[t.charAt(i)-'a']--; //遍历t字符串,统计字符出现次数在哈希表上以递减记录
    }
    for(int count:record){
        if(count!=0){
            return false; //统计所有的正负值相加,如果不为0,则s和t的字母数量对应不上
        }
    } return true; // // record数组所有元素都为零0,说明字符串s和t是字母异位词
    }
}

349. 两个数组的交换

set 不是万能用法,因为比数组慢。直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。

Java中用HashSet 做

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<>();
        //遍历数组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;
    }
}

resSet.stream(): resSet 是一个 Set<Integer> 集合。stream() 方法将这个集合转换为一个流 (Stream<Integer>),便于后续的操作。

mapToInt 属于中间操作(Intermediate Operation)的一种。这些操作是构建流水线的关键部分,用于转换和处理流中的元素,而不会触发任何实际的数据处理,这意味着它们是惰性的。

mapToInt 方法用于将对象流转换为一个原始的 int 流(IntStream)。这个方法接受一个函数作为参数,这个函数应用于流中的每个元素,并将它们映射为 int 类型的值。

它主要用于将非原始类型(如 IntegerString 等)的流元素转换成原始类型 int 的值。这对于性能优化是有好处的,因为原始类型流比对象流更加高效。

List<String> strings = Arrays.asList("Hello", "World");
int[] lengths = strings.stream().mapToInt(String::length).toArray();

在这个例子中,mapToInt 方法将 strings 流中的每个 String 元素映射到它们的长度(一个 int 值),然后 toArray() 方法将这个 IntStream 转换成一个 int 数组。

这里的 x -> x 是一个 lambda 表达式,表示对于流中的每一个元素 x(这里的 xInteger 类型),直接返回它的 int 值。因为 Integer 类型可以自动拆箱为 int,所以这个表达式实际上是在进行自动拆箱。

.toArray(): 这个方法将流中的元素收集到一个数组中。因为前面使用了 mapToInt,所以生成的是一个 int 类型的数组,而不是 Integer 类型的数组

😁Set是一个表示元素集合的接口,其中每个元素都是唯一的(不允许重复)。是<Integer>一个泛型类型参数,指示这Set将包含类型的元素Integer

set1可用于存储唯一整数的集合,通常用于各种操作,例如检查某些值是否存在、添加新值、删除值和迭代值。HashSet当主要操作涉及搜索元素时,使用是特别有利的,因为此类操作的平均时间复杂度为 O(1)。

new HashSet<>():这部分代码是a的实例化HashSet,是接口的具体实现SetHashSet由哈希表(实际上是一个实例)支持HashMap。它不保证其元素的顺序,这意味着迭代顺序可以与插入顺序不同。该<>运算符称为菱形运算符,告诉编译器从上下文推断泛型类型,在本例中为Integer

😁寻找交点:for (int i : nums2) { if (set1.contains(i)) { resSet.add(i); } }:该循环迭代nums2。对于 中的每个元素nums2,它检查该元素是否存在于set1(其中包含 的元素nums1)中。如果该元素同时存在于两个数组中,则将其添加到 中resSet,后者存储交集

方法二:用HashMap 写 

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        int[] hash1 = new int[1002];
        int[] hash2 = new int[1002];
        for(int i : nums1)
            hash1[i]++;
        for(int i : nums2)
            hash2[i]++;  //这些循环迭代nums1和nums2,增加相应哈希数组中每个元素的计数
        List<Integer> resList = new ArrayList<>(); //创建一个List命名resList来存储在两个数组中找到的元素。
        for(int i = 0; i < 1002; i++) //寻找交集元素
            if(hash1[i] > 0 && hash2[i] > 0) //如果在hash1和hash2中都找到某个元素,i则将其添加到。hash1hash2resList
                resList.add(i);
        int index = 0;
        int res[] = new int[resList.size()];
        for(int i : resList)
            res[index++] = i;
        return res;
    }
}

202.快乐数 

「快乐数」 定义为:

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

出现了 要快速找到一个元素(sum)是否出现在集合里的情况。----就用到HashMap

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> record = new HashSet<>();
        while (n != 1 && !record.contains(n)) { //n变成1:这意味着找到了一个快乐数,因为根据快乐数的定义,不断替换为其数字平方和的过程最终会得到1。
//record.contains(n)返回true:这意味着当前的数字n之前已经出现过,表明进入了一个无限循环,因此这个数不是快乐数。
            record.add(n); //这行代码将当前的数字n添加到HashSet record中。HashSet用于记录所有出现过的数字,以便检测是否进入了无限循环。
            n = getNextNumber(n); //这行代码调用getNextNumber方法,该方法接收当前的数字n,计算并返回下一个数字。
        }
        return n == 1;
    }

    private int getNextNumber(int n) {
        int res = 0;
        while (n > 0) {
            int temp = n % 10; //这行代码用于获取数字n的最后一位。操作符%表示取模,即取余数。因此,n % 10会得到n的个位数
            res += temp * temp; //这行代码计算得到的个位数的平方,并将其累加到变量res中。res是用来存储每一位数平方和的变量。
            n = n / 10;  //这行代码用于移除数字n的最后一位。操作符/表示整数除法。因此,n / 10会把n最右边的数字(即个位数)去掉,例如,如果n是123,那么执行n / 10之后,n会变成12。
        }
        return res;
    } //整个过程是将数字n的每一位数字分离出来,计算其平方,然后将这些平方数累加。
}

两数之和

示例:

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

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

所以返回 [0, 1]

解题 既需要下标又需要值 所以用Hash (数组空间局限而且会有浪费,set 是集合只能存key不能存value)

拿着一个数字(存进Hash表里的键值对)去找另一个符合条件的元素加入HashMap中

接下来需要明确两点:

  • map用来做什么
  • map中key和value分别表示什么

map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)

接下来是map中key和value分别表示什么。

这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。

那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。

所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。

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

public int[] twoSum(int[] nums, int target) {
    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];   // 对于每个元素 nums[i],计算 target 与其差值 temp
        if(map.containsKey(temp)){ //检查 map 中是否存在一个键(即一个数),它的值等于 temp。如果存在,表示我们找到了两个数,它们的和等于 target。
            res[1] = i; //如果找到了这样的一个数,将其索引(i)赋值给 res[1]。这表示数组 res 的第二个元素是当前遍历到的数组 nums 的索引
            res[0] = map.get(temp); //同时,获取 temp 在 map 中对应的值,即另一个加数的索引,赋值给 res[0]。这表示数组 res 的第一个元素是与当前元素配对的另一个元素的索引。
            break; //找到这两个数后,退出循环,因为任务已完成
        }
        map.put(nums[i], i);    // 如果没找到匹配对,就把访问过的元素和下标加入到map中
    }
    return res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值