Day6刷算法

哈希表

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法

但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

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

 

哈希表是根据关键码的值而直接进行访问的数据结构。

这么官方的解释可能有点懵,其实直白来讲其实数组就是一张哈希表

哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,如下图所示:

 

5fdee55a9c7232921eccc411df286fe2.png

哈希函数

哈希函数,把学生的姓名直接映射为哈希表上的索引,然后就可以通过查询索引下标快速知道这位同学是否在这所学校里了。

哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。

 

941da5f112b7b0201476cd35d30c5f1e.png

如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?

此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,这样我们就保证了学生姓名一定可以映射到哈希表上了。

常见的三种哈希结构

当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。

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

这里数组就没啥可说的了,我们来看一下set。

在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示:

集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::set红黑树有序O(log n)O(log n)
std::multiset红黑树有序O(logn)O(logn)
std::unordered_set哈希表无序O(1)O(1)

std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。

映射底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::map红黑树key有序key不可重复key不可修改O(logn)O(logn)
std::multimap红黑树key有序key可重复key不可修改O(log n)O(log n)
std::unordered_map哈希表key无序key不可重复key不可修改O(1)O(1)

std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。

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

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

其他语言例如:java里的HashMap ,TreeMap 都是一样的原理。可以灵活贯通。

虽然std::set和std::multiset 的底层实现基于红黑树而非哈希表,它们通过红黑树来索引和存储数据。不过给我们的使用方式,还是哈希法的使用方式,即依靠键(key)来访问值(value)。所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。std::map也是一样的道理。

这里在说一下,一些C++的经典书籍上 例如STL源码剖析,说到了hash_set hash_map,这个与unordered_set,unordered_map又有什么关系呢?

实际上功能都是一样一样的, 但是unordered_set在C++11的时候被引入标准库了,而hash_set并没有,所以建议还是使用unordered_set比较好,这就好比一个是官方认证的,hash_set,hash_map 是C++11标准之前民间高手自发造的轮子。

 

b39f4765a29f65e9299d5c5f9e59854a.png

摘录于代码随想录 

 242.有效的字母异位词

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

正确代码

class Solution {

    public boolean isAnagram(String s, String t) {

        int [] hash=new int[26];

        for(int i=0;i<s.length();i++){

            hash[s.charAt(i)-'a']++;

        }

        for(int i=0;i<t.length();i++){

            hash[t.charAt(i)-'a']--;

        }

        for(int i=0;i<26;i++){

            if(hash[i]!=0)

            return false;

        }

        return true;

    }

}

手稿

a5b9040992634cae8178329e65a7a2f2.jpg

 

我遇到的问题

1.怎么把一个数组的数据值都为0

int[ ] intArray = new int[5]; // 元素初始化为0  

2.s.length()和s.length有什么区别

在Java编程语言中,s.length() 和 s.length 这两个表达式有着本质的区别,主要体现在它们的应用对象和数据类型上。

  1. s.length():
    • 这个方法主要用于String类型的对象。
    • 它返回的是一个int类型的值,代表字符串s的长度,即字符串中字符的总数。
    • 值得注意的是,length()方法末尾有一个括号,这明确表明它是一个方法调用。
  2. s.length:
    • 这个属性或方法(取决于上下文)并不适用于String类型,但在其他场合可能出现。
    • 在Java的数组中,length是一个属性,用于获取数组的长度,它同样返回一个int类型的值。
    • 例如,若int[] arr = {1, 2, 3};,则arr.length会返回3,表示数组arr中有3个元素。
    • 对于String类型的对象,使用s.length是不合法的,因为String类中并没有定义这样的属性或不带参数的方法。

简而言之,当你处理String类型的对象时,应使用s.length()来获取字符串的长度;而当你需要了解数组的长度时,则应使用array.length(这里的array代表任意类型的数组)。这两者在Java中有着不同的用途和语法规则。如果你尝试在String对象上使用s.length,编译器会报错,提示你找不到该方法或属性。

3.为什么 hash[s[i]-'a']++;报错

s[i]t[i]替换为s.charAt(i)t.charAt(i)。因为String在内部使用char[]来存储字符),使用charAt()方法更符合Java的惯用法。

349. 两个数组的交集

题意:给定两个数组,编写一个函数来计算它们的交集。

使用数组来做哈希的题目,是因为题目都限制了数值的大小。

而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。

而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。

本题后面 力扣改了 题目描述 和 后台测试数据,增添了 数值范围:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

所以就可以 使用数组来做哈希表了, 因为数组都是 1000以内的。

std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_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> set2=new HashSet<>()

       for(int i:nums1){

            set1.add(i);

        }

        for(int i:nums2){

            if(set1.contains(i)){

                set2.add(i);

            }

        }

        /*return set2.stream()  

           .mapToInt(Integer::intValue)  // 使用 Integer::intValue 方法来转换  

           .toArray();  // 这将调用 IntStream 的无参 toArray 方法,返回 int[]*/

           //申请一个数组存放

           int[] arr=new int [set2.size()];

           int j=0;

           for(int i :set2){

            arr[j++]=i;

           }  return arr  }}

手稿

d9c758ab21504c529c66d7c2613be107.jpg

 

我遇到的问题

1.怎么创建两个HashSet类型的集合:set1和set2

Set<Integer> set1 = new HashSet<>();

Set<Integer> set2 = new HashSet<>();

2.怎么将结果集合转为数组,想要返回一个 int[] 数组

return set2.stream() .mapToInt(Integer::intValue) .toArray();

// 使用 Integer::intValue 方法来转换

Integer::intValue 是一个方法引用,它指向 Integer 类的 intValue 方法。这个方法是从 Integer 对象中提取其原始 int 值的标准方式。当你有一个 Integer 对象,但需要一个原始的 int 类型值时,你会调用这个方法。

// 这将调用 IntStream 的无参 toArray 方法,返回 int[]

3.或者说,怎么申请一个数组存放set2中的元素,然后返回数组

//方法2:另外申请一个数组存放setRes中的元素,最后返回数组

4.size和length有什么区别

  1. size:
    • 在Java中,size 通常用于表示集合(如ArrayListHashSetHashMap等)中元素的数量。
    • 它是通过调用集合类中的size()方法来获取的。
    • 例如,ArrayList<String> list = new ArrayList<>(); 你可以使用 list.size() 来获取列表中元素的数量。
  2. length:
    • length 通常用于表示数组的长度(即数组中元素的数量)。
    • 对于一维数组,你可以直接使用 .length 属性来获取数组的长度。
    • 例如,int[] numbers = {1, 2, 3, 4, 5}; 你可以使用 numbers.length 来获取数组的长度。
    • 需要注意的是,length 是一个属性,而不是一个方法,因此它后面不需要加括号。
    • length 也可以用于表示字符串的长度,因为字符串在Java中是通过字符数组来实现的。
    • 例如,String str = "Hello"; 你可以使用 str.length() 来获取字符串的长度(注意这里是方法,因为字符串类是对象化的,所以长度是通过方法获取的,尽管这个方法没有参数,并且看起来像属性)。

 

1. 两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

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

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

所以返回 [0, 1]

我遇到的问题

containsKey(balance)这是什么

containsKey(Object key) 是 Map 接口中的一个方法。这个方法用于检查调用它的 Map 对象(如 HashMapTreeMapLinkedHashMap 等)是否包含一个特定的键(key)。

这里的 balance 看起来是传递给 containsKey 方法的参数,意味着代码正在检查某个 Map 对象是否有一个键等于 balance 的条目。如果 Map 包含指定的键,则此方法返回 true;如果不包含,则返回 false

正确代码

class Solution {

    public int[] twoSum(int[] nums, int target) {

        Map<Integer,Integer> indexMap =new HashMap<>();

       

        for(int i=0;i<nums.length;i++){

            int s=target-nums[i];

            if(indexMap.containsKey(s))

            {

                return new int[]{i,indexMap.get(s)};}

            else{indexMap.put(nums[i],i);}

    }return null;}

}

手稿

de18ebff777942179e694782cd97e01d.jpg

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值