力扣刷题总结 哈希表(1)

博客主页: A_SHOWY
系列专栏:力扣刷题总结录 数据结构  云计算

242. 有效字母的异位词 easy 哈希映射(数组),排序算法
383. 救赎金 easy 暴力,两个for循环两遍,哈希和242题基本一样
349. 两个数组的交集 easy 使用数组或者集合都可以,数组更快
202. 快乐数 easy 取出来每一位的操作要熟悉
1. 两数之和 easy 使用map要注意pair,auto
454.四数相加Ⅱ mid map

一、哈希表理论基础

(1)定义

哈希表是根据关键码的值直接访问的数据结构,但是其实数组就是一张哈希表,哈希表的关键码就是所谓的索引下标,通过下标可以直接访问元素。

(2)哈希表解决的问题

哈希表一般解决的问题是:快速判断一个元素是否出现在集合中。

枚举的话时间复杂度是o(n),但是使用哈希表的话时间复杂度是o(1)。

(3)哈希函数

以查询学生的名字是否在学校里为例,把学生的姓名直接映射到哈希表上的索引,然后查询下标就可以知道他是否在学校

(4)哈希碰撞

当哈希函数完成后,小李和小王都映射到了索引下标为1的位置,叫做哈希碰撞。那么哈希碰撞的解决方法主要有

1.拉链法

以小陈和小国为例子,假如找到小陈和小国的索引都是1,则发生了冲突,那么冲突的元素都存在链表中 。

链表法需要注意选择适当的哈比表的大小,一来不会因为数组空值浪费太多内存,同时也不能让这个指针太长。

2.线性探测法

一定要保证数据规模小于哈希表的长度,需要哈希表的空位来解决碰撞的问题。

(5)常见的哈希结构

当使用哈希表来解决问题的时候,要用到三种数据结构

1.数组2.集合(set)3.映射(map)

当我们要快速查找一个元素的时候使用哈希表,但是哈希表也是牺牲了空间换取时间,因为我们要使用额外的数组,集合和映射存放数据。

二、使用数组的哈希表

(1) 242.有效字母的异位词

242. 有效的字母异位词icon-default.png?t=N7T8https://leetcode.cn/problems/valid-anagram/1.当我们看到数据很少,而且固定(限制数组的大小)时,当我们确定使用哈希表来解决问题时,优先使用数组来解决,是最省时间的。本题就是使用数组的哈希表最经典的一个题目

2.整体思路:判断是否是异位词,给了两个字符串,如果使用哈希表,且前提条件是确定是小写字母,那么先定义一个哈希数组不用记住a-z的ASCII码,直接s【i】- ‘a’做一个映射就可以,先对第一个字符串遍历一遍,如果出现a-z,对相应的哈希数组下标++。再对第二个字符串--,最后判断哈希数组中是否所有元素都是0

  (1) 方法一:哈希映射(数组)

class Solution {
public:
    bool isAnagram(string s, string t) {
//因为元素比较少,所以直接考虑用哈希的数组形式
int hash[26] = {0};
for(int i = 0;i < s.length(); i++)
{
    hash[s[i] - 'a']++;
}
for(int i = 0; i < t.length(); i++)
{
    hash[t[i] - 'a']--;
}
for(int i = 0; i<26; i++)
{
    if(hash[i] != 0 ) return false;
}
return true;
    }
};

  (2) 方法二:排序

class Solution {
public:
    bool isAnagram(string s, string t) {
//先判断一下长度是否同
if(s.length() != t.length())
return false;

//排序后看看是否相同
sort(s.begin(),s.end());
sort(t.begin(),t.end());
return s == t;
    }
}; 

排序算法同样可以解决这道题目,先看一下字符数量是否一致,然后排序后看是否相同。但是时间复杂度稍大o(nlogn),哈希映射的时间复杂度为o(n)。

(2) 383. 赎金信

383. 赎金信icon-default.png?t=N7T8https://leetcode.cn/problems/ransom-note/首先用暴力算法可以用两个for遍历两遍也可以通过

(1).方法一:暴力法

1.注意使用erase会消耗很多时间,同时字符串的erase写法后边括号里的是第几位

ransomNote.erase(ransomNote.begin() + j);//ransonNote删除出现过的这个字符
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
    for(int i = 0; i < magazine.length(); i++){
        for(int j = 0; j < ransomNote.length(); j++){
            if(magazine[i] == ransomNote[j]){
       ransomNote.erase(ransomNote.begin() + j);//ransonNote删除出现过的这个
            break;
            }
        }
    }
   if(ransomNote.length() == 0){
       return true;                  
   }
   return false;
    }
};

(2).方法二:哈希映射

仔细观察这个题,其实和前面做到的有效字母的异位词很像,那个题目是两个互相包含,而这个只需要一个包含一个

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
int hash[1000] = {0};
//先判断个数
if(magazine.length() < ransomNote.length()) return false;
for(int i = 0; i < magazine.length(); i++)
{
  hash[magazine[i] -'a'] ++;
}
for(int j =0; j < ransomNote.length(); j++)
{
    hash[ransomNote[j] - 'a'] --;

if(hash[ransomNote[j] - 'a'] < 0) return false;
}
return true;
    }
};

三、使用集合(set)的哈希表

(1)349.两个数组的交集

349. 两个数组的交集icon-default.png?t=N7T8https://leetcode.cn/problems/intersection-of-two-arrays/

力扣刷题总结 哈希表(1)_第1张图片

(1)方法一:使用unordered_set

1.确定用set以后,要求效率高,且去重所以使用unordered_set

2.代码理解上有一定难度for(int num : nums2)//指的是nums2中每个元素依次赋值给num

3.if(nums_set.find(num) != nums_set.end()   result_set.insert(num);这一段是查是否有相交的部分,如果在nums_set中找到了num,那么就放到result里面。

4.注意这道题目要求的是返回数组return vector(result_set.begin(),result_set.end());

class Solution {
public:
    vector intersection(vector& nums1, vector& nums2) {
//先创建两个容器
unordered_set result_set;//存放结果
unordered_set nums_set(nums1.begin(),nums1.end());//将nums1存放到哈希表中
for(int num : nums2)
{
    if(nums_set.find(num) != nums_set.end())
        result_set.insert(num);
}
//看好返回的是数组
return vector(result_set.begin(),result_set.end());
    }
};

(2)方法二:使用数组

1.当题目中给出了确定的范围1000的时候,其实这道题目更适合用数组哈希表来解决,效率更高,但是存储result为了去重,依然使用unordered_set。先将nums1给num,存到哈希表里,再检查nums2即可。

class Solution {
public:
    vector intersection(vector& nums1, vector& nums2) {
unordered_set result_set;//存放结果
int hash[1005] = {0};
for(int num : nums1)
{
   hash[num] = 1;
}
for(int num :nums2)
{
    if(hash[num] ==1) result_set.insert(num);
}
return vector(result_set.begin(),result_set.end());
    }
};

(2)202. 快乐数202. 快乐数icon-default.png?t=N7T8https://leetcode.cn/problems/happy-number/

1.题目的整体思路为:题目说明了可能出现循环,当出现循环的时候,就是死循环了,核心还是把这个每一次的sum存到一个哈希表里,然后当重复的时候就用循环,因为不能重复,当重复时候就是死循环所以还是用unordered_set。

2.这里要非常熟悉把每一位取出来的操作

int getsum(int n)
{
    int sum = 0;
    while(n)
    {
        sum += (n % 10) * (n % 10);
        n /= 10;
    }
    return sum;
}

3.后续的操作就是经典的集合哈希表找重复的过程

class Solution {
public:
int getsum(int n)
{
    int sum = 0;
    while(n)
    {
        sum += (n % 10) * (n % 10);
        n /= 10;
    }
    return sum;
}
bool isHappy(int n) {
unordered_set set;
while(1)
{
    int sum = getsum(n);//注意这里要重新定义sum,因为是新函数
    if(sum == 1) return true;
    if(set.find(sum) != set.end()) return false;
    else set.insert(sum);
    n = sum;
}       
    }
};

四、使用map的哈希表

(1)1.两数之和

1. 两数之和icon-default.png?t=N7T8https://leetcode.cn/problems/two-sum/

1.我们第一个是要考虑元素是否出现过,同时还要考虑下标,所以要用map,但是我们的核心是要找元素是否出现过,所以把元素作为key,下标作为val。map用来存放遍历过的元素

2.当用map存数据的时候,要考虑两个属性,一个是key值,一个是val值。当我我们输出的时候,要用pair把键值对两个值合成一个进行输出

map.insert(pair(nums[i],i));

3.值得注意的是,当我们在map里面找这个元素的时候,返回的是一个迭代器,但是试图将它赋值成int的话,两边类型不一致就会报错,所以使用auto关键字可以将右侧的表达式map.find(s)推导出迭代器的实际类型并赋值给int变量

auto iter = map.find(s); 

4.这个题目值得补充的地方很多,在map中,iter-first表示的是这个元素的key值,iter-second表示的是这个元素的val值。

5.注意区分length,length()和size()的区别

(1)length属性:用来获取数组长度  

int a = ar.length;

(2)length( )方法:用来获取字符串的长度

(3)size( )方法:获取泛型集合中有多少元素个数,例如数组的个数

for(int i = 0; i < nums.size(); i++)
class Solution {
public:
    vector twoSum(vector& nums, int target) {
    unordered_map map;//先做一个map存数据
    for(int i = 0; i< nums.size(); i++)
    {
        int s = target - nums[i];  
        auto iter = map.find(s); 
        if(iter != map.end())
        return {iter -> second,i};
        else map.insert(pair(nums[i],i));

    } 
    return {};
    }
};

(2)454. 四数相加Ⅱ

454. 四数相加 IIicon-default.png?t=N7T8https://leetcode.cn/problems/4sum-ii/

1.这个题目给了四个数组,如果直接暴力那么复杂度为o(n4)那么我们考虑将A和B数组的和为一组,C+D的和为一组进行存放后相加。那么除了要存放A+B的和还要存放出现的次数,所以要使用map哈希表。

2。我们考虑将A+B两个数组作为一个整体,C+D两个数组作为一个整体。A和B数组的和相加存储后,假如说a【i】+b【j】 = n(某个数)出现了三次,当我们在c+d= -5时,那么就出现了n个满足条件的四元组,count就加n。

3.在c++中指的是将a+b的值直接映射到map 的key中,如何存在则++,如果不存在就映射过去

map [a + b] ++;
class Solution {
public:
    int fourSumCount(vector& nums1, vector& nums2, vector& nums3, vector& nums4) {
unordered_map map;
for(int a : nums1)
{
    for(int b : nums2)
    {
        map [a + b] ++;//指的是将a+b的值直接映射到map 的key中,如何存在则++,如果不存在就映射过去
    }
}
int count = 0;//初始化计数
for(int c : nums3)
{
    for(int d : nums4)
    {
      if(map.find(0-c-d) != map.end())
      {
          count += map[-c-d];//这里需要注意,出现了几次,需要加上那个val不是加1
      }
    }
}
return count;
    }
};

你可能感兴趣的:(力扣刷题总结录,leetcode,算法,哈希算法,哈希表)