Leetcode练习题:哈希表


哈希表在之前的专题里面也是常常用到,是根据关键字的值直接进行访问,十分快速。虽然在学习数据结构的时候,哈希表的内容还是学的比较多的,包括各种解决地址冲突的方法,但都没有编码实现过,接下来的题目也是直接stl中的map来实现的,还是学习一下map在题目中的一些常用方法和套路。

30:串联所有单词的子串

问题描述

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例 1:

输入:

s = “barfoothefoobarman”,

words = [“foo”,“bar”]

输出:0 9

解释:

从索引 0 和 9 开始的子串分别是 “barfoo” 和 “foobar” 。输出时,按照索引由小到大顺序输出。

示例 2:

输入:

s = “wordgoodgoodgoodbestword”,

words = [“word”,“good”,“best”,“word”]

输出:-1

s中的子串无法由words串联得到,所以输出-1

解题思路

每个单词都要求出现,但不考虑顺序,因此可以建立一个哈希表,来记录每一个单词需要出现的次数。
从s的下标0来时向后扫描,因为单词长度是相同的,每一次切割下该长度的单词与所需哈希表比较,存在且map[string[>0,则将map[string]-1并继续扫描,如果都符合要求了,则记录该下标,这里使用count++来记录,不需要判断哈希表中每个字符的值是否都为0

代码实现

    vector<int> findSubstring(string s, vector<string>& words) {
    
       if (s == "" || words.size() == 0)
        {
            return {};
        }
        unordered_map<string, int> need;
        vector<int>ans;

        int word_len = words[0].size();
        int len = s.length();

        //记录所需字符以及出现次数
        for (auto& s: words)
        {
            need[s]++;
        }

        unordered_map<string,int> now;
        now=need;

        string temp = "";

        for (int i = 0; i < len; i ++)
        {
            //如果从该下标开始的后续长度已经不满足了话 直接退出
            if (len - i + 1 < word_len* words.size())
            {
                break;
            }
            //每次都重置now哈希表
            now=need;
            temp= s.substr(i, word_len);
            int j = i, count = 0;
            while (now[temp] > 0)
            {
                    now[temp]--;
                    count ++;
                    j += word_len;
                    temp = s.substr(j, word_len);
            }
            //利用count来实现所有字符串都完成
            if (count == words.size()) {
                ans.push_back(i);
            }
        }
        return ans;

    }

反思与收获

每个元素都需要出现,且不考虑出场顺序,这个时候利用哈希表十分方便,巧妙利用总数计数,免去了判断哈希表中每个值是否都为空的步骤

36:有效的数独

问题描述

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可:

数字 1-9 在每一行只能出现一次。

数字 1-9 在每一列只能出现一次。

数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

上图是一个部分填充的有效的数独。

数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

示例 1:

输入:

53…7…

6…195…

.98…6.

8…6…3

4…8.3…1

7…2…6

.6…28.

…419…5

…8…79

输出: true

示例 2:

输入:

83…7…

6…195…

.98…6.

8…6…3

4…8.3…1

7…2…6

.6…28.

…419…5

…8…79

输出: false

解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。

但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

说明:

一个有效的数独(部分已被填充)不一定是可解的。

只需要根据以上规则,验证已经填入的数字是否有效即可。

给定数独序列只包含数字 1-9 和字符 ‘.’ 。

给定数独永远是 9x9 形式的。

解题思路

每行设置一个哈希表,记录每个数字是否出现过
每列设置一个哈希表,记录每个数字是否出现过
每个大格设置一个哈希表,记录每个数字是否出现过
但由于只有9个元素,因此无需用到哈希表,直接数组解决了。
计算所在大格的标号为,i/3*3+j/3

代码实现

bool isValidSudoku(vector<vector<char>>& board) {
        int row[9][9];
        int column[9][9];
        int grid[9][9];

        for(int i=0;i<9;i++)
        {
            for(int j=0;j<9;j++)
            {
                row[i][j]=0;
                column[i][j]=0;
                grid[i][j]=0;
            }
        }
        int val,num;
        for(int i=0;i<9;i++)
        {
            for(int j=0;j<9;j++)
            {
                if(board[i][j]!='.')
                {
                    val=board[i][j]-'0'-1;
                    num=i/3*3+j/3;
                    if(row[i][val])
                    {
                        return false;
                    }else
                    {
                        row[i][val]=1;
                    }

                    if(column[j][val])
                    {
                        return false;
                    }else
                    {
                        column[j][val]=1;
                    }

                     if(grid[num][val])
                    {
                        return false;
                    }else
                    {
                        grid[num][val]=1;
                    }
                }

            }
        }
        return true;
    }

反思与收获

元素十分少的情况下,其实也可以直接用数组来实现哈希表的作用。

438:找到字符串中所有字母异位词

问题描述

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

说明:

字母异位词指字母相同,但排列不同的字符串。

示例 1:

输入:

s: “cbaebabacd” p: “abc”

输出:

[0, 6]

解释:

起始索引等于 0 的子串是 “cba”, 它是 “abc” 的字母异位词。

起始索引等于 6 的子串是 “bac”, 它是 “abc” 的字母异位词。

示例 2:

输入:

s: “abab” p: “ab”

输出:

[0, 1, 2]

解释:

起始索引等于 0 的子串是 “ab”, 它是 “ab” 的字母异位词。

起始索引等于 1 的子串是 “ba”, 它是 “ab” 的字母异位词。

起始索引等于 2 的子串是 “ab”, 它是 “ab” 的字母异位词。

解题思路

一次扫描遍历,有点像滑动窗口的感觉,控制窗口的左右边界。
首先利用哈希表来记录 所需字符以及对应出现的次数。
不断向右遍历,如果map[s[right]]>0的话,则说明该字符还是所需的,need++(这里need与上题count是相同作用)
当need的大小等于字符串p的大小时,说明每一个所需元素都扫描到了
再判断这一段扫描区间,因为left那可能含有多余的元素,如果map[s[left]]==0,则说明这个元素是所需,现在left往前走的话,need要减1了,因为如果不是所需的话,map[s[left]]应该是<0的

代码实现

vector<int> findAnagrams(string s, string p) {
        vector<int> res;
        int m[128] = {0};
        int left = 0, right = 0, need = 0;
        for(char c : p)
            ++m[c];

        while(right < s.size())
        {
            if(m[s[right]] > 0)     ++need;
            --m[s[right]];
            ++right;
            while(need == p.size())
            {
                if(right - left == p.size())   res.push_back(left);      //通过长度判断异位词,加入res中
                if(m[s[left]] == 0)     --need;
                ++m[s[left]];
                ++left;
            }
        }
        return res;
    }

反思与收获

滑动窗口结合哈希表,要想清楚left和right是在框柱考虑的字符区间,但是need才是真正在记录是否哈希表中每个元素都到位了的情况。

451:根据字符出现频率排序

问题描述

给定一个字符串,请将字符串里的字符按照出现的频率降序排列,如果频率相同,则按照字符的ASCII码升序排列。

示例 1:

输入:

“tree”

输出:

“eert”

解释:

'e’出现两次,'r’和’t’都只出现一次。

因此’e’必须出现在’r’和’t’之前,而且’r’比’t’的ASCII码小。

示例 2:

输入:

“cccaaa”

输出:

“aaaccc”

解释:

'c’和’a’都出现三次。因此按照字符升序排列,'a’在’c’前。

示例 3:

输入:

“Aabb”

输出:

“bbAa”

解释:

'A’和’a’被认为是两种不同的字符,并且’A’的ASCII码比’a’小

解题思路

建立哈希表,记录每个字符出现的次数,然后再根据次数进行排序,但是map没有排序这个操作,所以这边使用pair来实现对应的关系,vector来存储所有的pair,重写cmp函数

代码实现

static bool cmp(pair<char,int> p1,pair<char,int>p2)
    {
        return p1.second==p2.second ? p1.first<p2.first:p1.second>p2.second;
    }
    string frequencySort(string s) {
        int count[123]={0};
        for(int i=0;i<s.length();i++)
        {
            count[s[i]-'A'+65]++;
        }


        vector<pair<char,int>> m;

        for(int i=0;i<123;i++)
        {
            if(count[i]!=0)
            {
                //cout<<char(i)<<" "<<count[i]<<endl;
                m.push_back({(char)i,count[i]});
            }
        }

        sort(m.begin(),m.end(),cmp);
        string ans;
        for(auto v:m)
        {
            ans+=string(v.second,v.first);
        }
        return ans;
    }

反思与收获

想要对map里面继续排序,后面似乎有道题目会做到的692,但是利用vector<pair<string,int>>这样也能实现,适用于情况比较简单的场景

525:

问题描述

给定一个二进制数组, 找到含有相同数量的 0 和 1 的最长连续子数组(的长度)。

示例 1:

输入: [0,1]

输出: 2

说明: [0, 1] 是具有相同数量0和1的最长连续子数组。

示例 2:

输入: [0,1,0]

输出: 2

说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。

注意: 给定的二进制数组的长度不会超过50000。

解题思路

一开始想,搞个map记录0,1的数量,再判断数量是否相同,但这样找不到最长连续子数组的长度,因此下标我们肯定是要记录的,才能记录长度。
这里很巧妙的将,0看成-1,1看成+1,这样一来如果某个区间段和=0的话,则说明该区间0,1个数是相同。
既然如此,可以再想到类似前缀和的概念,一次遍历完成,利用哈希表记录每种前缀和出现的情况,如果之前就存在过,则说明这两段之间和为0,并且哈希表只要记录每种前缀和出现的第一次下标即可,才能计算最长长度

代码实现

int findMaxLength(vector<int>& nums) {
        unordered_map<int,int> m;
        m[0]=-1;
        int ans=0,count=0;
        for(int i=0;i<nums.size();i++)
        {
            count+=(nums[i]==1)?1:-1;
            if(m.find(count)!=m.end())
            {
                ans=max(ans,i-m[count]);
            }else
            {
                m[count]=i;
            }
        }
        return ans;
    }

反思与收获

利用哈希表记录前缀和,将0,1情况转换为-1和+1,这两种处理方法十分巧妙。

54:砖墙

问题描述

你的面前有一堵矩形的、由多行砖块组成的砖墙。 这些砖块高度相同但是宽度不同。你现在要画一条自顶向下的、穿过最少砖块的垂线。

砖墙由行的列表表示。 每一行都是一个代表从左至右每块砖的宽度的整数列表。

如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。你需要找出怎样画才能使这条线穿过的砖块数量最少,并且返回穿过的砖块数量。

你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。

示例:

输入:

 [[1,2,2,1],

  [3,1,2],

  [1,3,2],

  [2,4],

  [3,1,2],

  [1,3,1,1]]

输出: 2

解释:

砖墙如下:
在这里插入图片描述

解题思路

这题跟以前接触的区间题目还挺不一样,计算穿过这些区间,但是边缘不算。
如果直接去考虑穿过区间的线,是很难的因为有太多太多取值了,因此我们反着思考,来考虑恰好穿过边缘的线。

利用哈希表,将每一个右边缘长度出现的情况统计出来,原墙行数-出现情况最多次则为答案了。

代码实现

int leastBricks(vector<vector<int>>& wall) {
        unordered_map<int,int> m;
        int temp;
        for(int i=0;i<wall.size();i++)
        {
            temp=0;
            for(int j=0;j<wall[i].size()-1;j++)
            {
                temp+=wall[i][j];
                if(m[temp])
                {
                    m[temp]++;
                }
                else
                {
                    m[temp]=1;
                }
            }
        }

        int ans=0;
        for(auto i=m.begin();i!=m.end();i++)
        {
            ans=max(ans,i->second);
        }
        return wall.size()-ans;
    }

反思与收获

这题目还巧妙的,主要是利用哈希表,我们将什么设置为关键字值呢,什么是重要的需要记录并且有对应值的,在解决题目的时候要思考这些问题

560:和为K的子数组

问题描述

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2

输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

解题思路

经典前缀和和哈希表结合的题目。
用哈希表记录出现的前缀和情况,
后-前=K转换为前=后-K,如果哈希表中有后-K的元素,则将其出现的次数加到ans中

代码实现

int subarraySum(vector<int>& nums, int k) {
        unordered_map<int,int> m;
        m[0]=1;
        int ans=0,pre=0;
        for(int i=0;i<nums.size();i++)
        {
            pre+=nums[i];
            if(m.find(pre-k)!=m.end()) {ans+=m[pre-k];}
            m[pre]++;
        }
        return ans;
    }

反思与收获

前缀和与哈希表结合,还有前缀和的经典计算公式转换,真的很常用,要牢记,熟练掌握。

594:最长和谐子序列

问题描述

和谐数组是指一个数组(长度>=2)里元素的最大值和最小值之间的差别正好是1。

现在,给定一个整数数组,你需要在所有可能的子序列中找到最长的和谐子序列的长度。

如果找不到这样的数组,则输出0。

示例 :

输入: [1,3,2,2,5,2,3,7]

输出: 5

原因: 最长的和谐数组是:[3,2,2,2,3].

说明: 输入的数组长度最大不超过20,000.

解题思路

显然我们需要一个哈希表来记录每一个数出现的次数,那和谐数组是什么意思呢,注意这个子序列不要求连续,和谐数组,就是要么考虑当前元素和当前元素-1的元素个数和,要么考虑当前元素和当前元素+1的元素个数和。

代码实现

int findLHS(vector<int>& nums) {
        map <int,int> m;
        int ans=0;
        for(int i=0;i<nums.size();i++)
        {
            m[nums[i]]++;
            //如果前后元素都为0的话,就直接
            if(m[nums[i]-1]==0&&m[nums[i]+1]==0)
            {
                continue;
            }
            ans=max(ans,m[nums[i]]+max(m[nums[i]+1],m[nums[i]-1]));
        }
        return ans;
    }

反思与收获

这道题用哈希表解一下子就变得很简单了,了解和谐数列,最大于最小相差1,其实简单来理解,就是要么前面要么后面。

692:前K个高频单词

问题描述

给一非空的单词列表,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。

示例 1:

输入: [“i”, “love”, “leetcode”, “i”, “love”, “coding”], k = 2

输出: [“i”, “love”]

解析: “i” 和 “love” 为出现次数最多的两个单词,均为2次。

注意,按字母顺序 “i” 在 “love” 之前。

示例 2:

输入: [“the”, “day”, “is”, “sunny”, “the”, “the”, “the”, “sunny”, “is”,
“is”], k = 4

输出: [“the”, “is”, “sunny”, “day”]

解析: “the”, “is”, “sunny” 和 “day” 是出现次数最多的四个单词,

出现次数依次为 4, 3, 2 和 1 次。

解题思路

这题哈希表来记录每个单词出现的次数,然后根据次数排序,可以利用451的方式来实现,这边是直接对map进行排序顺序更改,注意cmp函数的写法

代码实现

 vector<string> topKFrequent(vector<string>& words, int k) {
        unordered_map<string,int> m;

        for(int i=0;i<words.size();i++)
        {
            m[words[i]]++;
        }

        sort(words.begin(),words.end(),[&](const string &s1,const string &s2){
             return (m[s1]>m[s2])||(m[s1]==m[s2]&&s1<s2);
             });
        unique(words.begin(),words.end());
        return vector<string>(words.begin(),words.begin()+k);
    }

反思与收获

直接在map中根据关键字的值进行排序,注意cmp函数重写,要添加[&]符号,const 参数

  **sort(words.begin(),words.end(),[&](const string &s1,const string &s2){
         return (m[s1]>m[s2])||(m[s1]==m[s2]&&s1<s2);}
         );**
 

781:森林中的兔子

问题描述

森林中,每个兔子都有颜色。其中一些兔子(可能是全部)告诉你还有多少其他的兔子和自己有相同的颜色。我们将这些回答放在 answers 数组里。

返回森林中兔子的最少数量。

示例:

输入: answers = [1, 1, 2]

输出: 5

解释:

两只回答了 “1” 的兔子可能有相同的颜色,设为红色。

之后回答了 “2” 的兔子不会是红色,否则他们的回答会相互矛盾。

设回答了 “2” 的兔子为蓝色。

此外,森林中还应有另外 2 只蓝色兔子的回答没有包含在数组中。

因此森林中兔子的最少数量是 5: 3 只回答的和 2 只没有回答的。

示例:

输入: answers = [10, 10, 10]

输出: 11

示例:

输入: answers = []

输出: 0

解题思路

一开始给人感觉竟然有点像逻辑题似的,但确实里面包含了一个隐藏信息那就是,只要是颜色相同的兔子它们报出来的数字都是一样的。
因此我们用哈希表来记录,同一种颜色报数过了的兔子个数。

之后我们才来分析,每一个颜色中,报数兔子个数,报的数,和该颜色兔子总数之间的关系。

注意:报出来数字相同的兔子其实也不一定颜色是相同的 比如 3 3 3 3 3,显然这里不止一种颜色的,那就再计算当中实现。

报的数3,则说明该颜色的兔子实际用3+它本身1=4只
现在有map[3]=5只兔子喊出了这样的话,celi[5/4]说明其实有两种颜色,两组这样的情况,则实际有4*2=8只

代码实现

int numRabbits(vector<int>& answers) {
        unordered_map<int,int> colors;
        int ans=0;
        for(int i=0;i<answers.size();i++)
        {
            colors[answers[i]]++;
        }

        for(auto i=colors.begin();i!=colors.end();i++)
        {
            int all=i->second;
            int mul=i->first+1;

            int time=ceil(all*1.0/mul);
            ans+=time*mul;

        }
        return ans;
    }

反思与收获

要挖掘题目隐藏的信息,和包含的逻辑关系,这个兔子题目还挺有趣的,利用哈希表来记录喊出相同数字的兔子数,再分析计算出总数。

——————————————————————————————————
哈希表真的运用很广泛,无论是专门的哈希表的题目还是其他的题目,在解决一对一映射问题,关键字记录的时候要考虑到哈希。还有简单情况时用数组来代替哈希,哈希的排序两种方法的解决,哈希结合前缀和,哈希结合滑动窗口的方法等。ヾ(๑╹◡╹)ノ"

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值