哈希表模块学习

242. 有效的字母异位词

题目介绍:

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

示例 1:

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

示例 2:

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

提示:
1 <= s.length, t.length <= 5 * 104
s 和 t 仅包含小写字母

思路:

哈希表主要用于快速查找某一元素是否存在,本题因为都是小写字母,故可以创建一个26位的数组record,随后先将字符串s包含的字母映射至数组(将s[i] - ‘a’ 即可),每个字母出现一次便加一,
再遍历字符串t,每个字母出现一次便减一。若最后数组内的元素都为0,则说明是字母异位词。
具体代码如下:

class Solution {
public:
    bool isAnagram(string s, string t) {
        int record[26] = {0} ;  //定义数组,记录字母出现的次数
        for( int i = 0 ; i < s.size() ; i++ ){
            //与‘a'相减得到ASCII码作为该字母的索引
            record[ s[i] - 'a' ]++ ;
        }
        for( int i = 0 ; i < t.size() ; i++ ){
            record[ t[i] - 'a' ]-- ; 
        }
        for(int i = 0 ; i <26 ; i++){
            //如果存在不为0的元素,说明一定存在一个字符串多了或者少了字符
            if( record[i] != 0 )    return false ; 
        }

        return true ; 
    }
};

383. 赎金信

题目介绍:

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

提示:
1 <= ransomNote.length, magazine.length <= 105
ransomNote 和 magazine 由小写英文字母组成

思路:

1、判断ransomNote 的长度是否大于magazine的长度
2、创建record数组,先记录magazine的所有字母及数量,字母每出现一次,数量加一
3、遍历ransomNote ,字母每出现一次数量减一
4、遍历数组是否存在小于0的元素,若小于0则表示magazine中缺少此字母
具体代码如下:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        //如果 ransomNote 的长度大于magazine的长度直接返回false
        if(ransomNote.size() > magazine.size() )  return false ; 
        int record[26] = {0} ; 
        //记录magazine的所有字母及数量
        for( char mag : magazine ){
            ++record[ mag - 'a' ] ; 
        }
        
        for( char ran : ransomNote){
            --record[ ran - 'a' ] ;
        }
        //若有元素小于0,则代表magazine中缺少此字母
        for( int i =0 ; i < 26 ; i++ ){
            if( record[i] < 0 ) return false ;
        } 

        return true ;
    }   
};

49. 字母异位词分组

题目介绍:

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

输入: strs = [""]
输出: [[""]]

示例 3:

输入: strs = ["a"]
输出: [["a"]]

提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i] 仅包含小写字母

思路:

1、先创建mapStr用于记录字母及数量,其中key形式为字母+数量,如:a1b2f3,value为key对应的异位词集合
2、遍历strs,创建数组record记录下每个元素的字母及数量
3、遍历数组拼接出key,并存入mapStr中
4、遍历map,将所有map的value值传入result中
具体代码如下:

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map< string , vector<string> > mapStr ;   
        vector<vector<string> > result; 

        for( string str : strs )
        {
            int record[26] = {0} ; 
            //记录下str的字符,及其个数
            for(int i = 0 ; i < str.size() ; i++ ){
                record[ str[i] - 'a' ]++ ; 
            }
            string key = "" ; 
            for(int i = 0 ; i < 26 ; i++){
                //拼接出key
                if( record[i] != 0 ){
                    key += ( i + 'a' ) ;
                    key += to_string( record[i] ) ; 
                }
            }
            mapStr[ key ].push_back( str ) ;
        }
        
        for( auto &it : mapStr)
        {
            result.push_back( it.second ) ; 
        }

        return result ; 
    }
};

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

题目介绍:

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 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" 的异位词。

提示:
1 <= s.length, p.length <= 3 * 104
s 和 p 仅包含小写字母

思路:

方法一:
1、判断两个字符串长度,若字符串s的长度小于p的长度,返回空
2、创建vector容器countS ,countP,遍历字符串p,同时也从0开始,长度为p的长度遍历字符串s,获取p各个字母的出现次数,以及s的前lenP各个字母的出现次数
3、判断两个vector是否相等,相等则为异位词
4、建立从0开始,长度为lenP的滑动窗口,每次移动一位,向后移动lenS-lenP位,判断后续是否还存在异位词
具体代码如下:

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        //滑动窗口
        //之所以是vector而不是普通数组是因为vector有内置函数,可以直接比较两个容器是否相等
        vector<int> countS(26) ; 
        vector<int> countP(26) ;
        
        int lenS = s.size() ; 
        int lenP = p.size() ; 
        vector<int> result ; 
        
        if( s.size() < p.size() )       return {} ;     //当字符串s的长度小于p的长度时,不存在子串
        //获取p各个字母的出现次数,以及s的前lenP各个字母的出现次数
        for( int i = 0 ; i < p.size() ; i++ )
        {
            ++countP[ p[i] - 'a' ] ;
            ++countS[ s[i] - 'a' ] ;
        }

        if( countP == countS )  result.push_back(0)  ;  //两个数组相等则表明为异位词

        for( int i = 0 ; i < lenS -lenP ; i++)
        {
            //滑动窗口向后移一位
            --countS[ s[i] - 'a' ] ;  
            ++countS[ s[i+lenP] - 'a'] ;
            
            if( countP == countS )  result.push_back( i+1 ) ;
        }

        return result ;
    }
};

方法二:
优化滑动窗口,不需要再每次比较两个数组是否相同,而是通过differ值(滑动窗口内字符串与p的不同字母或者相同字母数量不同的数量)来判断。
滑动窗口优化后具体代码如下:

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        //滑动窗口优化
        vector<int> count(26) ; 

        int lenS = s.size() ; 
        int lenP = p.size() ; 
        vector<int> result ; 

        int differ = 0 ;  //滑动窗口内字符串与p的不同字母或者相同字母数量不同的数量
        
        if( s.size() < p.size() )       return {} ;     //当字符串s的长度小于p的长度时,不存在子串
        //获取p各个字母的出现次数,以及s的前lenP各个字母的出现次数
        for( int i = 0 ; i < p.size() ; i++ )
        {
            ++count[ s[i] - 'a' ] ;
            --count[ p[i] - 'a' ] ;
        }
        //判断两者differ值
        for( int i= 0 ; i < 26 ; i++ )
        {
            if( count[i] != 0 ) ++differ ; 
        }
        if( differ == 0 )  result.push_back(0) ;  //当differ等于0时,表示两个字符串为字母异位词

        for( int i = 0 ; i < lenS -lenP ; i++)
        {
            //移除窗口起始元素
            //表示本来窗口内本字母的数量与p相同,但因为窗口后移,此字母数量将变得不同,differ加一
            if( count[ s[i] - 'a'] == 0 )   ++differ ;  
             //表示本来窗口内本字母的数量与不同且正好多一个,但因为窗口后移,此字母数量将变得相同,differ减一
            if( count[ s[i] - 'a'] == 1 )   --differ ; 
            //窗口后移,此字母数量减一
            --count[ s[i] - 'a'] ; 
            
            //窗口新增元素
            //表示本来窗口内本字母的数量与p相同,但因为窗口新增了此字母,此字母数量将变得不同,differ加一
            if( count[ s[i+lenP] -'a' ] == 0 )  ++differ ;
            //表示本来窗口内本字母的数量与不同且正好少一个,但因为窗口新增此字母,此字母数量将变得相同,differ减一
            if( count[ s[i+lenP] -'a' ] == -1 ) --differ ;
            //窗口后移,此字母数量加一
            ++count[ s[i+lenP] -'a' ] ;

            if(differ == 0 )  result.push_back(i+1) ; 
        }

        return result ;
    }
};

349. 两个数组的交集

题目介绍:

给定两个数组 nums1 和 nums2 ,返回 它们的交集。
输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000

思路:

1、创建unordered_set保存nums1的所有元素
2、遍历nums2,每遍历一个元素便判断set中是否包含此元素,若包含则是交集
具体代码如下:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result ;        //用于存储结果
        unordered_set<int> nums( nums1.begin() , nums1.end() ) ;    //使用nums记录下num1所有不重复的元素

        for( int num : nums2 )
        {
            //若nums中存在此元素,说明此元素是两者的交集
            if( nums.find(num) != nums.end() )     result.insert( num ) ;
        } 
        //转换为vector类型输出
        return vector<int>(result.begin() , result.end() ) ;
    }
};

202. 快乐数

题目介绍:

编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false

提示:
1 <= n <= 231 - 1

思路:

1、先写一个方法获取每个数的平方之和
2、创建set来记录每次出现的平方之和
3、建立一个死循环,在循环内不断获取各位数的平方之和,直至出现和为1的情况或者出现set中已经记录的数字
具体代码如下:

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

    bool isHappy(int n) {
        unordered_set<int> set  ;   //用于保存已经出现过的数
        while(1){
            int sum = getSum(n) ;   //获取各位数平方和
            if(sum == 1 )   return true ;   //若sum为1说明是快乐数,返回true
            if( set.find(sum) != set.end() )    return false ;      //若set中已经存在此数了,说明已经陷入了死循环,不是快乐数
            else set.insert(sum ) ;     //若set中不包含此数,将其添加
            n = sum ; 
        }
    }
};

1. 两数之和

题目介绍:

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案

思路:

1、创建map记录已经遍历过的数字,key为nums中的元素,value为该元素的下标
2、遍历nums,每遍历一个元素便判断map中是否包含target-nums[i],若存在则返回结果,不存在则向map中添加本次遍历的元素
具体代码如下:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int ,int> map ;   //创建map记录已经遍历过的数字,key为nums中的元素,value为该元素的下标
        for( int i = 0 ; i < nums.size() ; i++ ){
            auto it = map.find( target - nums[i] ) ; 
            if( it != map.end() )   return { it->second , i } ; //若map中包含另一个数,返回i以及该数的下标
            else map[ nums[i] ] = i ;   //若map中不包含该数,将其添加
        }
        return {} ;
    }
};

454. 四数相加 II

题目介绍:

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

提示:
n == nums1.length
n == nums2.length
n == nums3.length
n == nums4.length
1 <= n <= 200
-228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228

思路:

1、创建map记录nums1+nums2所有的结果,key为和,value为出现过的次数
2、遍历nums1、nums2,记录所有的nums1 + nums2的结果
3、遍历nums3+nums4 ,若umap中存在 0-c-d 这个数,count加该数出现过的次数
具体代码如下:

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int , int>  umap ; //记录nums1+nums2所有的结果,key为和,value为出现过的次数
        int count = 0  ;    
        //记录所有的nums1 + nums2的结果
        for( int a : nums1){
            for( int b : nums2 ){
                ++umap[ a+b ] ;
            }
        }
        //遍历nums3+nums4
        for( int c : nums3 ){
            for( int d : nums4 ){
                //若umap中存在 0-c-d 这个数,count加该数出现过的次数
                if( umap.find( 0-c-d ) != umap.end() )  count += umap[0-c-d] ; 
            }
        }

        return count ; 
    }
};

15. 三数之和

题目介绍:

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:
3 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5

思路:

1、先对nums进行排序
2、遍历nums,判断nums[i] > 0 ,如果第一个元素就大于0,则等式不存在,直接返回空,并对a去重
3、创建左右指针,左指针为i+1,右指针指向nums.size()-1 , 而后便是双指针的循环,left<right。
4、还需注意对b和c进行去重
具体代码如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        //双指针
        vector<vector<int>> result; 
        sort(nums.begin() , nums.end() ) ;     //先将其排序
        //寻找a+b+c = 0 
        for(int i = 0 ; i < nums.size() ; i++ )
        {
            if( nums[i] > 0 )   return result ; //如果第一个元素就大于0,则等式不存在,返回空
            if( i > 0 && nums[i] == nums[i-1] ) continue ;  //对a去重
            int left = i+1  , right = nums.size() - 1 ; 

            while( left < right )
            {
                //若三数之和大于0,right左移
                if( nums[i] + nums[left] + nums[right] > 0 )    right-- ;
                //若三数之和小于0,left右移
                else if( nums[i] + nums[left] + nums[right] < 0 )  left++ ; 
                else{
                    result.push_back( {nums[i] , nums[left] , nums[right] })    ;
                    //对c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    //对b去重
                    while (right > left && nums[left] == nums[left + 1]) left++;
                    //找到第一组结果,左右指针继续移动
                    right-- ; 
                    left++  ;
                }
            }  
        }
        return result ;
    }
};

18. 四数之和

题目介绍:

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109

思路:

与上题三数之和的思路以及方法都是一样的,只是在剪纸去重时更为复杂,需要注意。
具体代码如下:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int> >  result ; 
        sort(nums.begin() , nums.end()) ; 
        for( int i = 0 ; i < nums.size() ; i++)
        {
             //若第一个元素就大于target,直接退出此层循环,记得一定要加上条件nums[i] >=0
            if( nums[i] > target && nums[i] >= 0 )  break ;    
            if( i > 0 && nums[i] == nums[i-1] )  continue ; //对a去重
            for( int j = i +1 ; j < nums.size() ; j++ )
            {
                //若两个元素相加就大于target,直接退出此层循环,记得一定要加上条件nums[i] + nums[j] >= 0
                if( nums[i] + nums[j] > target && nums[i] + nums[j] >= 0 )    break ; 
                //对b去重
                if( j > i + 1  && nums[j] == nums[j-1] )   continue ;
                int left = j +1  , right = nums.size() - 1 ;
                while( left < right ){
                    //记得转成long类型,否则会溢出
                    //当和大于target时,右指针左移
                    if( (long)nums[i] + nums[j] + nums[left] + nums[right] > target )     right-- ;
                    //当和小于target时,左指针右移
                    else if( (long)nums[i] + nums[j] + nums[left] + nums[right] < target )    left++ ; 
                    else{
                        result.push_back( { nums[i] , nums[j] , nums[left] , nums[right] }) ;
                        //去重
                        while( right > left && nums[right] == nums[right-1] ) right-- ;     
                        while( right > left && nums[left] == nums[left+1] ) left++ ;
                        //得到一组结果后,左右指针移动
                        right-- ;
                        left++ ; 
                    }
                }
            }
        }
        return result ; 
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值