哈希表实际问题(LeetCode)

哈希映射

  1. 场景I - 提供更多信息
    使用哈希映射的第一个场景是,我们需要更多的信息,而不仅仅是键。然后通过哈希映射建立密钥与信息之间的映射关系

  2. 场景II - 按键聚合
    另一个常见的场景是按键聚合所有信息,我们也可以使用哈希映射来实现这一目标。

哈希表STL

1. 下面的问题是一个数组求交集的问题,利用内置的STL可以找到很多解决的办法,下面是改进过程和代码:

  • 这个想的复杂了,利用unordered_map来实现检索和输出,可是问题是,题目要求去重,这个比较麻烦,遍历超时,其实还是可以用setinsert特性去重的(真笨OVO)。主要是去重使用了暴力,导致不能\(AC\).
//代码丢了。。。写法简单不想重写
  • 把一个数组放到set中去重,在接下来遍历比较跟第二个数组的内容,把重复的子集装到新的容器,这样就去重检索出来了
//版本2:去重借助STL的hashset,大幅度降低搜索时间复杂度
// #include<vector>
// #include<unordered_map>
using namespace std;
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> num;
        unordered_set<int> nums2_set(nums2.begin(),nums2.end());
        for(int num1 : nums1)
            if(nums2_set.count(num1) == 1) num.insert(num1);
        return vector<int> (num.begin(),num.end());
    }
};
  • 第三个利用的内置的set_intersection()函数库函数还提供了set_difference函数set_union函数,分别指的是差集合和并集```.
// #include<vector>
// #include<unordered_map>
using namespace std;
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        vector<int> res;
        set<int> set_1(nums1.begin(),nums1.end());
        set<int> set_2(nums2.begin(),nums2.end());
        set_intersection(set_1.begin(),set_1.end(),set_2.begin(),set_2.end(),insert_iterator<vector<int>>(res,res.begin()));
    return res;
    }
};
  • 下面是交集的实现:库函数
//实现
template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_intersection (InputIterator1 first1, InputIterator1 last1,
                                 InputIterator2 first2, InputIterator2 last2,
                                 OutputIterator result)
{
  while (first1!=last1 && first2!=last2)
  {
    if (*first1<*first2) ++first1;
    else if (*first2<*first1) ++first2;
    else {
      *result = *first1;
      ++result; ++first1; ++first2;
    }
  }
  return result;
}

2.Happy_number

  1. 定义
    「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。

    如果 n 是快乐数就返回 True ;不是,则返回 False 。

  2. 思路
    访问一个数字,要么最后到达\(1\),要么最后无限循环。
    分析
    根据我们的探索,我们猜测会有以下三种可能。

    1. 最终会得到\(1\)
    2. 最终会进入循环。
    3. 值会越来越大,最后接近无穷大。
      第三个情况比较难以检测和处理。我们怎么知道它会继续变大,而不是最终得到\(1\)呢?我们可以仔细想一想,每一位数的最大数字的下一位数是多少。
    Digits Largest Next
    1 9 81
    2 99 162
    3 999 243
    4 9999 324
    13 9999999999999 1053
    对于\(3\)位数的数字,它不可能大于\(243\)。这意味着它要么被困在\(243\)以下的循环内,要么跌到\(1\)\(4\)位或\(4\)位以上的数字在每一步都会丢失一位,直到降到\(3\)位为止。所以我们知道,最坏的情况下,算法可能会在\(243\)以下的所有数字上循环,然后回到它已经到过的一个循环或者回到\(1\)。但它不会无限期地进行下去,所以我们排除第三种选择。
  3. 代码

class Solution {
public:
    int getSum(int n){
        int res = 0;
        while(n){
            res += pow(n%10,2);
            n /= 10;
        }
        return res;
    }
    bool isHappy(int n) {
       set<int> set_bar;
       while(n != 1){
           n = getSum(n);
           if(set_bar.find(n) != set_bar.end()){//STL函数,如果找到就返回它的**迭代器**,否则返回尾部迭代器
               n = -1;
               break;
           } 
           else set_bar.insert(n);
           //n = res;
       }
       return n == 1;
    }
};

3. 两数之和

  1. 题目描述
    给定一个整数数组\(nums\)和一个目标值\(target\),请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
    你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

    示例:给定\(nums = [2, 7, 11, 15], target = 9\),因为\(nums[0] + nums[1] = 2 + 7 = 9\),所以返回\([0, 1]\)

  2. 代码

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

4. 两个列表的最小索引总和

  1. 题目描述
    假设Andy和Doris想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示。
    你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅。 如果答案不止一个,则输出所有答案并且不考虑顺序。 你可以假设总是存在一个答案。

    示例 1:
    输入:

    \[["Shogun", "Tapioca Express", "Burger King", "KFC"] \]

    \[["Piatti", "The Grill at Torrey Pines", "Hungry Hunter Steakhouse", "Shogun"] \]

    输出: \(["Shogun"]\)
    解释: 他们唯一共同喜爱的餐厅是“Shogun”。

    示例 2:
    输入:

    \[["Shogun", "Tapioca Express", "Burger King", "KFC"] \]

    \[["KFC", "Shogun", "Burger King"] \]

    输出: \(["Shogun"]\)
    解释: 他们共同喜爱且具有最小索引和的餐厅是“Shogun”,它有最小的索引和1(0+1)。

    提示:

    • 两个列表的长度范围都在 [1, 1000]内。
    • 两个列表中的字符串的长度将在[1,30]的范围内。
    • 下标从0开始,到列表的长度减1。
    • 两个列表都没有重复的元素。
  2. 思路
    首先我们为了便于查找,需要建立一个哈希表,来减小搜查的开销,其次我们需要做的就是在搜查list2的时候需要注意我们应该要记录两者的索引之和,再把这个string保存在一个vector里面,随之,我们每次检查跟之前的索引和的大小关系,如果小的话,我们就需要清除之前存储在vector里面的比较大的值,再把接下来的小的压入;在遇到相同的索引和的时候,我们也需要一块儿压入,不过不需要清除vector

  3. 实现代码

class Solution {
public:
    vector<string> findRestaurant(vector<string>& list1, vector<string>& list2) {
        vector<string> res;//存放可能的字符串的列表
        unordered_map<string,int> t;//哈希表
        int min = 2001;
        
        for(int i = 0; i < list1.size(); i++)
            t[list1[i]] = i;

        for(int i = 0; i < list2.size(); i++){
            if(t.count(list2[i]) > 0){
                if(t[list2[i]] + i < min){//下标和大于,清空重压
                    res.clear();
                    min = t[list2[i]] + i;
                    res.push_back(list2[i]);
                }
                else if(t[list2[i]] + i == min)//相等,直接压入
                    res.push_back(list2[i]);
            }
        }
        return res;
    }
};

5.存在重复的元素

  1. 题目描述
    给定一个整数数组和一个整数k,判断数组中是否存在两个不同的索引i和j,使得 nums[i] = nums[j],并且i和j的差的绝对值至多为k。

2.思路

  1. 想了很久这个题目,最开始的想法是利用hashmap记录数组的内容和它的下标,以此来判断是否重合,并算出distance,满足要求就好了,思路比较简单,但是实现起来需要注意逻辑,需要在赋值之前就要判断是否存在,这样比较简单.

  2. 还有一种思路是leetcode官方的思路,比较不容易想得到,我们利用一个hashset,来维护一个大小为\(k\)滑动窗口,要是在这个窗口里面找到了重复的元素,返回true,否则返回false,滑动窗口的维护工作需要我们在每一次插入新的元素之后都要判断一次:是否窗口容量增加?如果是的话,就需要把之前最旧的元素删除掉: hash.erase(nums[i-k]),以此来维护滑动窗口。

  3. 实现

//版本1:map存储
class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_map<int, int> map;
        for (int i = 0; i < nums.size(); i++) {
            if (map.find(nums[i]) != map.end() && i - map[nums[i]] <= k) return true;
            map[nums[i]] = i;
        }
        return false;
    }
};
//版本2:滑动窗口版本
class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_set<int>hash;
        for(int i = 0; i < nums.size(); i++){
            if(hash.count(nums[i])) return true;
            hash.insert(nums[i]);
            if(hash.size() > k)
                hash.erase(nums[i-k]);
        }
        return false;
    }
};

6. 字母异位词分组

  1. 题目描述
    给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
    示例:
    输入:

    ["eat", "tea", "tan", "ate", "nat", "bat"]
    

    输出:

    [
      ["ate","eat","tea"],
      ["nat","tan"],
      ["bat"]
    ]
    

    说明:
    所有输入均为小写字母。
    不考虑答案输出的顺序。

  2. 思路
    我们应当知道,想要在整个的列表当中找到同构的字符串不很容易,但是我们可以利用map的去重特性,得到的是一个关于队列的map,很容易得到结果,当然想到这一步很不容易:

 vector<vector<string>>res;
 unordered_map<string,vector<string>>ans;

LeetCode上面还有一种代替的方式,计算的方式会更加高效,实现的思路查不了太多。

  1. 代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>>res;
        unordered_map<string,vector<string>>ans;//存储排序之后的键和它的值
        for(string s : strs){
            string t = s;
            sort(t.begin(),t.end());
            ans[t].push_back(s);//放到vector当中
        }
        for(auto list : ans)//list是一个包含着key和value的pair;
            res.push_back(list.second);
        return res;  
    }
};
posted @ 2020-10-29 18:23  Marvel_Iron_Man  阅读(106)  评论(0)    收藏  举报