首页 > 其他分享 >刷这几道LeetCode,掌握哈希表的三种类型

刷这几道LeetCode,掌握哈希表的三种类型

时间:2023-09-26 23:22:36浏览次数:46  
标签:map set 数组 nums int 几道 vector 哈希 LeetCode

基础知识

常用代码

  哈希表一共有3种哈希结构,分别是数组、set(集合)、map(映射)

数组

  数组就是把不同的元素映射到不同的地址运用数组创建哈希表,应当遵循以下两个原则:

1.所映射的元素的数值种类不多(比如26个字母)
2.映射关系比较好表达(比如26个字母,就可以用该元素-'a'作为映射)

set(集合)作为哈希表

  数组大小有限,如果题目没有限制数值大小,且数组空间交大,哈希值较少,会容易造成浪费,这个时候我们就可以考虑使用set。
C++一共给set提供了以下三种数据结构,可以按照表中的效率,如果要查找的话,很明显unordered_set的效率更高一点,set和multiset的元素是有序的,区别在于,multiset的元素可以重复:

  以下是set创建插入删除查询遍历的语法,其他两类只需要把set替换成multiset/unordered_set:

#include <set> // 包含头文件

int main() {
    // 创建一个std::set
    std::set<int> mySet;

    // 插入元素
    mySet.insert(10);
    mySet.insert(20);
    mySet.insert(30);

    // 查找元素
    if (mySet.find(20) != mySet.end()) {
        // 元素存在
    }

    // 删除元素
    mySet.erase(10);

    // 遍历元素
    for (const auto& element : mySet) {
        // 使用 element
    }

    return 0;
}

map(映射)作为哈希表

  map不同于set,map的每个元素对应一个value和一个key,能很快查询到的值是key值,C++同样也给map提供了3种数据结构:


,当需要统计一个元素出现的次数时,可以考虑使用map,以下是map的语法规则

  //创建
  std::unordered_map <int,int> map;
  // 遍历当前元素,并在map中寻找是否有匹配的key
   auto iter = map.find(target - nums[i]); 
   if(iter != map.end()) {
       return {iter->second, i};
    }
    // 如果没找到匹配对,就把访问过的元素和下标加入到map中
    map.insert(pair<int, int>(nums[i], i)); 

刷题总结

数组

LeetCode242有效的字母异词位

  思路:把26个字母映射到一个长度为26的数组中,并通过数组的值去判断出现频率,最后遍历return。
  解题犯的错:第一次写吧数组的初始化写错了,真的是太久没有写代码手生了。
  本题很符合哈希表数组的要求,26个字母正好可以通过-'a'对应26个连续的地址,元素不多,映射关系比较好找,具体代码如下:

class Solution {
public:
    bool isAnagram(string s, string t) {
        //int hash[26]=0; 错误的数组初始化
        int hash[26]={0};
        int i=0;
        for(i=0;i<s.size();i++)
        {
            hash[s[i]-'a']++;
        }
        for(i=0;i<t.size();i++)
        {
            hash[t[i]-'a']--;
        }
        for(i=0;i<26;i++)
        if(hash[i]!=0) return false;
        return true;

    }
};

LeetCode383赎金信

  这道题我第一次解用了map,把元素作为key值,把出现的次数作为value,代码很简单,如下:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map<char,int> base;
        for(char a:magazine)
        base[a]++;
        for(char b:ransomNote)
        {
            if(base.find(b)!=base.end()&&base[b]>0)
            base[b]--;
            else
            return false;
        }
        return true;
    }
};

  但这一题明显和上一题很类似,用数组空间复杂度会更低一些,思路还是和上题一样,26个地址对应26个字母,数组的值表示出现的次数,以及用数组一定要记得初始化,代码如下:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        //这里记得要初始化啊,我刚开始没有初始化一个测试样例就没通过
        int hash[26]={0};
        //这里分出一种情况判断可以省下一部分时间
        if(ransomNote.size()>magazine.size())
        return false;
        for(char a:magazine)
        hash[a-'a']++;
        for(char b:ransomNote)
        {
            if(hash[b-'a']>0)
            hash[b-'a']--;
            else
            return false;
        }
        return true;
    }
};

set

LeetCode349两个数组的交集

  这题的思路就是把nums1中出现过的元素,存放到一个哈希表中,可以是unordered_set,也可以是数组(因为数组元素大小是0<x<=1000),然后通过nums2的查询,把能在哈希表中查询到的元素放入一个unordered_set(不用去重)中,以下是源代码:
两个unordered_set:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set;
        unordered_set<int> nums_set(nums1.begin(),nums1.end());
        for(int num:nums2)
        {
            if(nums_set.find(num)!=nums_set.end())
            result_set.insert(num);
        }
        return vector<int>(result_set.begin(),result_set.end());

    }
};

一个数组一个unordered_set:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //哈希表数组做法
        unordered_set<int> result_set;
        //不要忘记初始化
        int hash[1000]={0};
        for(int i=0;i<nums1.size();i++)
        {
            hash[nums1[i]]=1;
        }
        for(int num:nums2)
        {
            if(hash[num]==1)
            result_set.insert(num);
        }
        return vector<int>(result_set.begin(),result_set.end());
    }
};

LeetCode202快乐数

  思路:首先写一个summ函数,计算不同位数的平方和,本题的关键在于,如果不是快乐数,很有可能陷入无限循环中去,而这时候n可能会重复出现,我们正是利用这一点,创建一个unordered_set,每次计算完去查找有没有重复的数字,如果有重复的说明已经进入循环,直接return false。
  所犯错误:因为本人比较菜,所以写的时候在一些地方卡住了:

1.while里面填什么,刚开始真的晕了好大一会儿,现在写总结文章的时候才觉得,既然可能是无限循环,通过return结束,直接写while(1)不就好了吗?
2.最好不要把函数名和变量名写得一样,我写得一样就报错了
3.最后!!!因为前面的代码太长,所以写到最后容易忘记最关键的一步,重新给n赋值!!!

代码如下:

class Solution {
public:
int summ(int n)
{
    int sum=0;
    while(n)
    {
        sum+=(n%10)*(n%10);
        n=n/10;
    }
    return sum;
}

    bool isHappy(int n) {
        unordered_set<int> result_set={0};
        //某个憨憨在这里想了半天要while什么,返回去发现卡哥写了while(1)
        while(1)
        {
            //我刚开始把函数名也名为sum,结果就报错了,这里函数名和变量名应该不能相同
            int sum=summ(n);
            if(sum==1) 
            return true;
            else if(sum!=1)
            {
                if(result_set.find(sum)!=result_set.end())
                return false;
                else
                result_set.insert(sum);
                //第一次写忘记加了
                n=sum;
            }
        }
    }
};

map

LeetCode1两数之和

  思路:用一层for循环去遍历数组,边遍历边把元素存入到 unordered_map数据类型中,以元素值为key,数组中的下标为value(因为题目要求返回下标),然后去查找map中有没有值为target-nums[i]的值,如果有就返回二者下标,没有就把这个元素存入并继续遍历,具体代码如下:

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

LeetCode454四数相加II

  四个数组前两个创建一个unordered_map,用key值记录两数相加之和,用value值记录这个和出现的次数,剩下的思路和上题类似,遍历剩下的两个数组,寻找有没有int target=-(c+d),并把符合条件的key值加给count,具体代码如下:

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> map;
        int count=0;
        for(int a:nums1)
        {
            for(int b:nums2)
            {
                map[a+b]++;
            }
        }
        for(int c:nums3)
        {
            for(int d:nums4)
            {
                int target=-(c+d);
                if(map.find(target)!=map.end())
                {
                    count+=map[target];
                }
            }
        }
        return count;

    }
};

双指针法

  总所周知,哈希表章节可以不用哈希表(狗头),以下两题思路类似,只是多了一层for循环,因此展开细讲其中一道:

LeetCode15两数之和

LeetCode18四数之和
  看到这道题,本能还是和两数之和一样,想使用哈希法,但是三个数字要去重,实行起来会很麻烦,所以后来听了卡哥的双指针法感觉让人眼前一亮。
  思路:首先对数组进行升序排序,然后让i从第一个遍历到最后一个,设置左右两个指针,分别指向i的后一个和最后一个元素,如果nums[i] (本层循环的最小值)大于0,那么后面的就不用进行。
  用一个while循环来改变左右两个指针,最终返回
  尽管如此,本题的难点依旧在去重操作上,首先是对i的去重,我们判断nums[i]和nums[i-1]是否相等,如果相等的话,这重循环就不用进行了,用continue跳过;
  其次是对left和right的去重,要放到while循环的末尾位置,确保如果符合条件的话,有一组数据已经写入result数组中,本题的源代码如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++)
        {
            int left=i+1;
            int right=nums.size()-1;
            if(nums[i]>0)
            return result;
            if(i>0&&nums[i]==nums[i-1]) continue;
            while(left<right)
            {
                if(nums[i]+nums[left]+nums[right]<0)
                left++;
                //我的天呐,这里第一次少打了一个else就错了,选择语句没有那么简单
                else if(nums[i]+nums[left]+nums[right]>0)
                right--;
                else
                {
                    result.push_back(vector<int>{nums[i],nums[left],nums[right]});
                    left++;
                    right--;
                    //这里我一开始用的是if,但是有很多测试用例通过不了,后来改成while报错,再后来加上了left<right才正确
                    while(nums[left]==nums[left-1]&&left<right) left++;
                    while(nums[right]==nums[right+1]&&left<right) right--;
                }
            }
        }
        return result;
    }
};

  我这里把while和if用错了,这里要注意应该是while而不是if,因为重复值可能不止一个。

总结

  1.这两天时间比较赶,后面可以认真看一下哈希表的有关知识,了解一下哈希表的原理。
  2.要坚持呀要坚持呀!加油!

标签:map,set,数组,nums,int,几道,vector,哈希,LeetCode
From: https://www.cnblogs.com/hellozznx/p/17731550.html

相关文章

  • leetcode17、77
    回溯算法可以当作是二叉树的结构进行分析,看在叶节点的位置是什么条件收获结果每个抛进去的结果都是到叶子节点的路径以leetcode17为例:每一层遍历的是每一个号码对应的字符串,当号码全部遍历完成就可以返回结果,所以终止条件是(index==string.length());index是层数,string是号码。......
  • [LeetCode] 2582. Pass the Pillow
    Thereare n peoplestandinginalinelabeledfrom 1 to n.Thefirstpersoninthelineisholdingapillowinitially.Everysecond,thepersonholdingthepillowpassesittothenextpersonstandingintheline.Oncethepillowreachestheendofthel......
  • LeetCode54.螺旋数组
    本题关键在于模拟数组螺旋的步骤,使用 flag 二维数组标识矩阵某位置是否被访问过,使用 turn 变量指示当前寻找的方向, turn 为0时,代表向右查找, turn 为1时,代表向下查找, turn 为2时,代表向左查找, turn 为3时,代表向上查找,具体的代码如下:classSolution{publicList<......
  • 算法训练day20 LeetCode654
    算法训练day20LeetCode654.617.700.98654.最大二叉树题目654.最大二叉树-力扣(LeetCode)题解代码随想录(programmercarl.com)使用递归返回节点地址,输入父节点地址,数组终止条件是输入地数组为空单层操作:如果输入数组为空,则返回父节点地址否则找到数组中最大......
  • 关联式数据结构_哈希表剖析 #C++
    哈希概述哈希(hash)又称散列,其基本想法是,将存储的值与其存储位置建立某种映射,因此哈希的查找效率非常高,是一种支持常数平均时间查找的结构。与红黑树相比,哈希的效率表现是以统计为基础的,不需要依赖输入数据的随机性。建立值-址映射建立哈希结构的第一步是将“值”(数据)与“址”(存......
  • Java -【字符串,数组,哈希表】常用操作
    一.字符串创建字符串:可以使用双引号或者String类的构造方法创建字符串。Stringstr1="HelloWorld";Stringstr2=newString("HelloWorld");连接字符串:可以使用加号或者String类的concat()方法连接字符串。Stringstr3=str1+str2;Stringstr4=str1.concat(str2);......
  • Go - 【字符串,数组,哈希表】常用操作
    一.字符串字符串长度:s:="hello"l:=len(s)fmt.Println(l)//输出5遍历字符串:s:="hello"fori,c:=ranges{fmt.Printf("%d:%c",i,c)}//输出:0:h1:e2:l3:l4:ofori:=0;i<len(s);i++{ fmt.Printf("%s",s[......
  • Java -【字符串,数组,哈希表】常用操作
    一.字符串创建字符串:可以使用双引号或者String类的构造方法创建字符串。Stringstr1="HelloWorld";Stringstr2=newString("HelloWorld");连接字符串:可以使用加号或者String类的concat()方法连接字符串。Stringstr3=str1+str2;Stringstr4=str1.concat(str2);获......
  • LeetCode 918. 环形子数组的最大和
    环形子数组的最大和(medium)题目链接:918.环形子数组的最大和题目描述:给定一个长度为n的环形整数数组nums,返回nums的非空子数组的最大可能和。环形数组意味着数组的末端将会与开头相连呈环状。形式上,nums[i]的下一个元素是nums[(i+1)%n],nums[i]的前一个元素......
  • LeetCode 53. 最大子数组和
    最大子数组和(medium)题目链接:53.最大子数组和题目描述:给你一个整数数组nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。示例1:输入:nums=[-2,1,-3,4,-1,2,1,-5,4]输出:6解释:连续子数组[4,-1,2,1]的和最大......