LeetCode(七)哈希表专题

LeetCode 1. Two Sum

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int>hash;
        for(int i = 0; i < nums.size(); i ++)
        {
            int t = target - nums[i];
            if(hash.count(t)) return vector<int>({hash[t], i}); //hash[2] = 0
            hash[nums[i]] = i; //插入hash表 {2,0} 值和 下标
        }
        return vector<int>();        
    }
};

LeetCode 454. 4Sum II

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。

例如:

输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]

输出:
2

解释:
两个元组如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
class Solution {
public:
    /*
    1.建立 unordered_map 哈希表,存储数组A和数组B所有数对求和出现的次数。
    2.枚举数组C和数组D的所有数对,查询哈希表中 -c - d出现的次数,累计到答案中。
    */
    int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
        unordered_map<int, int> hash; //key:两数之和 val:出现的次数
        int res = 0;
        for(auto a : A)
            for(auto b : B)
                hash[a + b] ++;
        for(auto c : C)
            for(auto d : D)
                res += hash[-c-d];
        return res;
    }
};

LeetCode 560. Subarray Sum Equals K

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

示例 1 :

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

说明 :

数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。

class Solution {
public:
    //nums[i ... j] = sum[j] - sum[i - 1]
    /*
    1.对原数组求前缀和后,一个和为 k 的子数组 即为 一对 前缀和的差值为 k 的位置
    2.遍历前缀和数组,用 unordered_map 哈希表记录每个前缀和出现的次数。
    特别地,初始时前缀和为 0 需要被额外记录一次。
    3.遍历过程中,对于当前前缀和 tot,累计之前 tot - k 前缀和出现的次数累积到答案即可。
    */
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int>hash;
        hash[0] = 1;
        int res = 0, s = 0;
        for(auto x : nums)
        {
            s += x;
            res += hash[s - k];
            hash[s]++;
        }
        return res;
    }
};

LeetCode 525. Contiguous Array

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

示例 1:

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

示例 2:

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

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

class Solution {
public:
    /*
    1.将数组中的 0 视作 -1,则求连续相同 0 和 1 个数的子数组就是求连续和为 0 的子数组。
    2.连续子数组的和可以用两个前缀和相减得到,故这里就是求下标差距最大的两个相等的前缀和。
    3.使用哈希表记录前缀和及其下标。遍历时,若当前的前缀和在哈希表中出现,则更新答案;否则将其值和下标加入哈希表。
    4.注意哈希表中需要初始化一个前缀和 0。
    */
    int findMaxLength(vector<int>& nums) {
        unordered_map<int, int> hash; //记录前缀和 和前缀和对应的下标
        hash[0] = -1;
        int res = 0, s = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            s += nums[i] ? 1 : -1;
            if(hash.count(s)) res = max(res, i - hash[s]);
            else hash[s] = i; 
        }
        return res;
    }
};

LeetCode 187. Repeated DNA Sequences

所有 DNA 由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。

编写一个函数来查找 DNA 分子中所有出现超多一次的10个字母长的序列(子串)。

示例:

输入: s = “AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT”

输出: [“AAAAACCCCC”, “CCCCCAAAAA”]

class Solution {
public:
    vector<string> findRepeatedDnaSequences(string s) {
        unordered_map<string, int> hash;//字符串 出现次数
        vector<string> res;
        for(int i = 0; i + 10 <= s.size(); i++)
        {
            string str = s.substr(i, 10);//起始 分割长度
            if(++hash[str] == 2) res.push_back(str);
        }
        return res;
    }
};

LeetCode 347. Top K Frequent Elements

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

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

示例 2:

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

说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。

class Solution {
public:
    /*
    首先用哈希表统计出所有数出现的次数。
    由于所有数出现的次数都在 1 到 n 之间,所以我们可以用计数排序的思想,统计出次数最多的前 k 个元素的下界。然后将所有出现次数大于等于下界的数输出。
    */
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        for(auto x : nums) hash[x] ++; //插入所有数
        int n = nums.size();
        vector<int> s(n + 1, 0);//统计出现次数的数组
        for(auto p : hash) s[p.second] ++;//遍历哈希表 统计个数
        int i = n, t = 0;
        while(t < k) t += s[i--];
        vector<int>res;
        for(auto p : hash)
            if(p.second > i)
                res.push_back(p.first);
        return res;        
    }
};

LeetCode 350. Intersection of Two Arrays II

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

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

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]

说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。

进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果nums2的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

class Solution {
public:
    /*
    首先先将nums1存入哈希表中,注意这里要用unordered_multiset<int>,而不是unordered_set<int>,因为数组中含有重复元素。
    然后遍历nums2,对于每个数 x,如果 x 出现在哈希表中,则将 x 输出,且从哈希表中删除一个 x。
    */
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        if(nums1.size() > nums2.size()) return intersect(nums2, nums1);//判断大小
        unordered_multiset<int> hash;
        for(auto x : nums1) hash.insert(x);
        vector<int> res;
        for(auto x : nums2)
        {
            if(hash.count(x) > 0)
            {
                res.push_back(x);
                auto it = hash.find(x);
                hash.erase(it);
            }
        }
        return res;
    }
};

LeetCode 706. Design HashMap

不使用任何内建的哈希表库设计一个哈希映射

具体地说,你的设计应该包含以下的功能

put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。
get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。
remove(key):如果映射中存在这个键,删除这个数值对。

示例:

MyHashMap hashMap = new MyHashMap();
hashMap.put(1, 1);
hashMap.put(2, 2);
hashMap.get(1); // 返回 1
hashMap.get(3); // 返回 -1 (未找到)
hashMap.put(2, 1); // 更新已有的值
hashMap.get(2); // 返回 1
hashMap.remove(2); // 删除键为2的数据
hashMap.get(2); // 返回 -1 (未找到)

注意:
所有的值都在 [1, 1000000]的范围内。
操作的总数目在[1, 10000]范围内。
不要使用内建的哈希库。

class MyHashMap {
public:
    /** Initialize your data structure here. */
    const static int N = 20011;
    int hash_key[N], hash_value[N];
    MyHashMap() {
        memset(hash_key, -1, sizeof hash_key);//初始化为-1
    }
    
    int find(int key)
    {
        int t = key % N;
        while(hash_key[t] != key && hash_key[t] != -1)//如果key不相同 而且 该位置不为空
        {
            if(++t == N) t = 0;
        }
        return t;
    }
    
    /** value will always be non-negative. */
    void put(int key, int value) {
        int t = find(key);
        hash_key[t] = key;
        hash_value[t] = value;
    }
    
    /** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
    int get(int key) {
        int t = find(key);
        if(hash_key[t] == -1) return -1;
        return hash_value[t];
    }
    
    /** Removes the mapping of the specified value key if this map contains a mapping for the key */
    void remove(int key) {
        int t = find(key);
        if(hash_key[t] != -1)
            hash_key[t] = -2;//把删除的位置赋值为-2
    }
};

/**
 * Your MyHashMap object will be instantiated and called as such:
 * MyHashMap* obj = new MyHashMap();
 * obj->put(key,value);
 * int param_2 = obj->get(key);
 * obj->remove(key);
 */

LeetCode 652. Find Duplicate Subtrees

给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。

两棵树重复是指它们具有相同的结构以及相同的结点值。

示例 1:

    1
   / \
  2   3
 /   / \
4   2   4
   /
  4

下面是两个重复的子树:

  2
 /
4

4

因此,你需要以列表的形式返回上述重复子树的根结点。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    unordered_map<string, int>hash;// 树 树出现的次数
    vector<TreeNode*> ans;
    
    string dfs(TreeNode * u)
    {
        if(!u) return "NULL";//遍历完没有返回空
        string left = dfs(u->left);
        string right = dfs(u->right);
        string s = to_string(u->val) + ',' + left + ',' + right;//前序遍历
        if(++hash[s] == 2) ans.push_back(u);
        return s;
    }
        
    vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
        dfs(root);
        return ans;
    }
};

LeetCode 290. Word Pattern

给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。

示例1:

输入: pattern = “abba”, str = “dog cat cat dog”
输出: true

示例 2:

输入:pattern = “abba”, str = “dog cat cat fish”
输出: false

示例 3:

输入: pattern = “aaaa”, str = “dog cat cat dog”
输出: false

示例 4:

输入: pattern = “abba”, str = “dog dog dog dog”
输出: false

说明:
你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。

class Solution {
public:
    bool wordPattern(string pattern, string str) {
        stringstream raw(str); //读入字符流 以空格分割
        vector<string> words;
        string word;
        while(raw >> word) words.push_back(word); //raw 相当于 cin
        unordered_map<char, string> PS;//看pattern 是不是和 str 对应
        unordered_map<string, char> SP;
        if(pattern.size() != words.size()) return false;
        for(int i = 0; i < words.size(); i++)
        {
            char p = pattern[i];//字符
            string s = words[i];//字符串
            if(!PS.count(p)) PS[p] = s;//插入
            if(!SP.count(s)) SP[s] = p;
            if(PS[p] != s || SP[s] != p) return false;
        }
        return true;
    }
};

LeetCode 554. Brick Wall

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

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

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

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

示例:

输入: [[1,2,2,1],
[3,1,2],
[1,3,2],
[2,4],
[3,1,2],
[1,3,1,1]]

输出: 2

解释:

(tu)

提示:
每一行砖块的宽度之和应该相等,并且不能超过 INT_MAX。
每一行砖块的数量在 [1,10,000] 范围内, 墙的高度在 [1,10,000] 范围内, 总的砖块数量不超过 20,000。

class Solution {
public:
    /*
        缝隙看成点 统计点出现的次数 点重合最多的
    */
    int leastBricks(vector<vector<int>>& wall) {
        unordered_map<int, int>hash;//边界位置 出现次数
        int res = 0;
        for(auto blocks : wall)
        {
            int s = 0;
            for(int i = 0; i + 1 < blocks.size(); i++)//求每行每块边界的点
            {
                s += blocks[i];
                hash[s] ++;//每出现一次加一
                res = max(res, hash[s]);
            }
        }
        return wall.size() - res;
    }
};

LeetCode 149. Max Points on a Line

给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。

示例 1:

输入: [[1,1],[2,2],[3,3]]
输出: 3
解释:

^
|
|        o
|     o
|  o  
+------------->
0  1  2  3  4

示例 2:

输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出: 4
解释:

^
|
|  o
|     o        o
|        o
|  o        o
+------------------->
0  1  2  3  4  5  6
class Solution {
public:
    /*
    对两点计算斜率 对斜率做hash.
    先枚举一个定点,然后将其它点按斜率分组,斜率指与定点连线的斜率,分组可以利用哈希表。
    由于一个定点加斜率可以唯一确定一条直线,所以被分到同一组的点都在一条直线上。组中点数的最大值就是答案。
    特殊情况:
    1.竖直直线不存在斜率,需要单独计数;
    2.与定点重合的点可以被分到所有组中,需要单独处理;
    避免精度问题,斜率用 long double 存储。
    */
    int maxPoints(vector<vector<int>>& points) {
        if(points.empty()) return 0;
        int res = 1;//一个点 
        for(int i = 0; i < points.size(); i++)
        {
            int ver = 1, dup = 0; //竖直方向的点 定点重合的点
            for(int j = i + 1; j < points.size(); j ++)
                if(points[i][0] == points[j][0])
                {
                    ver ++;
                    if(points[i][1] == points[j][1]) dup ++;                        
                }
            unordered_map<long double, int> hash;//斜率 出现次数
            for(int j = i + 1; j < points.size(); j ++)
                if(points[i][0] != points[j][0])
                {
                    long double slope = (long double)(points[i][1] - points[j][1]) / (points[i][0] - points[j][0]);
                    if(!hash.count(slope)) hash[slope] = 2;//斜率不存在 直接返回两点
                    else hash[slope] ++;
                    res = max(res, hash[slope] + dup);
                }
            res = max(res, ver);
        }
        return res;
    }
};

LeetCode 355. Design Twitter

设计一个简化版的推特(Twitter),可以让用户实现发送推文,关注/取消关注其他用户,能够看见关注人(包括自己)的最近十条推文。你的设计需要支持以下的几个功能:

postTweet(userId, tweetId): 创建一条新的推文
getNewsFeed(userId): 检索最近的十条推文。每个推文都必须是由此用户关注的人或者是用户自己发出的。推文必须按照时间顺序由最近的开始排序。
follow(followerId, followeeId): 关注一个用户
unfollow(followerId, followeeId): 取消关注一个用户

示例:

Twitter twitter = new Twitter();

// 用户1发送了一条新推文 (用户id = 1, 推文id = 5).
twitter.postTweet(1, 5);

// 用户1的获取推文应当返回一个列表,其中包含一个id为5的推文.
twitter.getNewsFeed(1);

// 用户1关注了用户2.
twitter.follow(1, 2);

// 用户2发送了一个新推文 (推文id = 6).
twitter.postTweet(2, 6);

// 用户1的获取推文应当返回一个列表,其中包含两个推文,id分别为 -> [6, 5].
// 推文id6应当在推文id5之前,因为它是在5之后发送的.
twitter.getNewsFeed(1);

// 用户1取消关注了用户2.
twitter.unfollow(1, 2);

// 用户1的获取推文应当返回一个列表,其中包含一个id为5的推文.
// 因为用户1已经不再关注用户2.
twitter.getNewsFeed(1);

class Twitter {
public:
    /** Initialize your data structure here. */
    unordered_map<int, vector<pair<int, int>>>posts;
    unordered_map<int, unordered_set<int>>follows;
    int id = 0;
    Twitter() {
        
    }
    
    /** Compose a new tweet. */
    void postTweet(int userId, int tweetId) {
        posts[userId].push_back(make_pair(id ++, tweetId));
    }
    
    /** Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */
    vector<int> getNewsFeed(int userId) {
        vector<pair<int, int>>ps;
        for(auto x : posts[userId]) ps.push_back(x); //用户post
        for(auto follow : follows[userId])
            for(auto x : posts[follow])
                ps.push_back(x);
        sort(ps.rbegin(), ps.rend());//逆序
        //c.rbegin() 返回一个逆序迭代器,它指向容器c的最后一个元素
        //c.rend() 返回一个逆序迭代器,它指向容器c的第一个元素前面的位置
        vector<int> res;
        for(int i = 0; i < 10 && i < ps.size(); i++)
            res.push_back(ps[i].second);
        return res;
    }
    
    /** Follower follows a followee. If the operation is invalid, it should be a no-op. */
    void follow(int followerId, int followeeId) {
        if(followerId != followeeId)
            follows[followerId].insert(followeeId);
    }
    
    /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */
    void unfollow(int followerId, int followeeId) {
        follows[followerId].erase(followeeId);
    }
};

/**
 * Your Twitter object will be instantiated and called as such:
 * Twitter* obj = new Twitter();
 * obj->postTweet(userId,tweetId);
 * vector<int> param_2 = obj->getNewsFeed(userId);
 * obj->follow(followerId,followeeId);
 * obj->unfollow(followerId,followeeId);
 */

LeetCode 128. Longest Consecutive Sequence

给定一个未排序的整数数组,找出最长连续序列的长度。

要求算法的时间复杂度为 O(n)。

示例:

输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。

class Solution {
public:
    /*
    从前往后扫描整个数组,过程中用两个哈希表unordered_map<int,int>tr_left, tr_right维护所有连续整数序列,两个哈希表分别将序列的左右端点映射成序列长度。
    
    当我们遍历到一个新的数 x 时,先查找 x 左右两边分别存在多长的连续序列,两个值分别是tr_right[x-1]和tr_left[x+1],分别记为left和right,此时我们可以将左右两部分和 x拼起来,形成一个更长的连续整数序列,然后更新新序列的左右两端的值:
    1.左端点是 x-left,更新:tr_left[x - left] = max(tr_left[x - left], left + 1 + right);
    2.右端点是 x+right,更新:tr_right[x + right] = max(tr_right[x + right], left + 1 + right);
    最后我们不要忘记用新序列的长度left+right+1更新答案。
    */
    int longestConsecutive(vector<int>& nums) {
        int res = 0;
        unordered_map<int, int>tr_left, tr_right;
        for(auto &x : nums)
        {
            // [__________x-1]x[x+1_________]
            int left = tr_right[x - 1];//以x-1为右端点的最大长度区间
            int right = tr_left[x + 1];//以x+1为左端点的最大长度区间
            tr_left[x - left] = max(tr_left[x - left], left + 1 + right);
            tr_right[x + right] = max(tr_right[x + right], left + 1 + right);
            res = max(res, left + 1 + right);
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值