Code Capriccio - Summary of Hash Tables

Hash table

Hash table theoretical basis

The three common hash structures are divided into: array, set, and map
. In C++, set and map provide the following three data structures respectively. Their underlying implementation and advantages and disadvantages are shown in the following table:

gather underlying implementation Is it in order? Whether the value can be repeated Can the value be changed? Query efficiency Addition and deletion efficiency
std::set red black tree orderly no no O(log n) O(log n)
std::multiset red black tree orderly yes no O(logn) O(logn)
std::unordered_set Hash table disorder no no O(1) O(1)

The underlying implementation of std::unordered_set is a hash table. The underlying implementation of std::set and std::multiset is a red-black tree. The red-black tree is a balanced binary search tree, so the key values ​​​​are ordered, but the key It cannot be modified. Changing the key value will cause chaos in the entire tree, so it can only be deleted and added.

mapping underlying implementation Is it in order? Whether the value can be repeated Can the value be changed? Query efficiency Addition and deletion efficiency
std::map red black tree key ordered key cannot be repeated key cannot be modified O(logn) O(logn)
std::multimap red black tree key ordered key can be repeated key cannot be modified O(log n) O(log n)
std::unordered_map Hash table key unordered key cannot be repeated key cannot be modified O(1) O(1)

The underlying implementation of std::unordered_map is a hash table, and the underlying implementation of std::map and std::multimap is a red-black tree. In the same way, the keys of std::map and std::multimap are also ordered.

When we need to quickly determine whether an element appears in a set, we must consider hashing.

  • Arrays: When fast retrieval and storage are neededfixed size elementsWhen, you should use an array
  • set: when neededStore without duplicationelements and need to be retrieved quickly, set should be used
  • map: when neededStore key-value pairs, and when fast retrieval is required, map should be used

unordered_set common operations

  • insert element
    • insert(element) : Insert an element into unordered_set. If the element already exists, it will not be inserted.
  • Delete element
    • erase(element) : Delete a specified element from unordered_set. If the element does not exist, it will not be deleted.
    • clear() : Clear all elements in unordered_set.
  • query element
    • find(element) : Finds a specific element and returns an iterator pointing to its position. If it cannot be found, it returns an end() iterator.
    • count(element) : Determine whether an element exists in unordered_set. If it exists, return 1, otherwise return 0.
  • Other operations
    • size(): Returns the number of elements in unordered_set.
    • empty(): Determine whether unordered_set is an empty set, if so, return true, otherwise return false.
#include <iostream>
#include <unordered_set>
using namespace std;

int main() {
    
    
    unordered_set<int> uset;

    // 插入元素
    uset.insert(1);
    uset.insert(2);
    uset.insert(3);

    // 删除元素
    uset.erase(2);

    // 查找元素
    auto it = uset.find(3);
    if (it != uset.end()) {
    
    
        cout << "Found " << *it << endl;
    }

    // 获取元素个数
    cout << "Size: " << uset.size() << endl;

    // 判断元素是否存在
    if (uset.count(2)) {
    
    
        cout << "2 exists" << endl;
    } else {
    
    
        cout << "2 does not exist" << endl;
    }

    // 清空元素
    uset.clear();

    // 判断是否为空
    if (uset.empty()) {
    
    
        cout << "Empty set" << endl;
    }

    // 遍历元素
    for (auto x : uset) {
    
    
        cout << x << " ";
    }
    cout << endl;
    return 0;
}

unordered_map common operations

  • insert element
    • insert(pair<key, value>) : Insert a key-value pair in unordered_map. If the key (key) already exists, update the value (value).
  • Delete element
    • erase(key) : Delete the element of the specified key (key) from unordered_map. If the key does not exist, it will not be deleted.
    • clear() : Clear all key-value pairs in unordered_map.
  • query element
    • find(key) : Finds a specific key and returns an iterator pointing to its location. If it cannot be found, it returns an end() iterator.
    • count(key) : Determine whether a key exists in unordered_map. If it exists, return 1, otherwise return 0.
  • Other operations
    • size(): Returns the number of key-value pairs in unordered_map.
    • empty(): Determine whether unordered_map is an empty set, if so, return true, otherwise return false.
#include <iostream>
#include <unordered_map>   // 引入 unordered_map 头文件
using namespace std;

int main() {
    
    
  
  // 创建一个空的 unordered_map
  unordered_map<int, string> myMap;
  
  // 添加键值对(insert 或 emplace)
  myMap.insert({
    
    1, "apple"});
  myMap.emplace(2, "banana");
  
  // 使用 [] 运算符访问元素,注意:如果元素不存在,会自动创建一个默认值
  cout << myMap[1] << endl;    // 输出:apple
  
  // 查找并访问元素,使用 find 方法获取迭代器进行访问,注意:如果没有找到,返回值为 end()
  auto it = myMap.find(2);
  if (it != myMap.end()) {
    
    
    cout << it->second << endl;   // 输出:banana
  }
  
  // 遍历 unordered_map
  for (auto& [key, value]: myMap) {
    
    
    cout << "key: " << key << ", value: " << value << endl;
  }
  
  // 判断 unordered_map 是否为空
  if (myMap.empty()) {
    
    
    cout << "myMap is empty" << endl;
  } else {
    
    
    cout << "myMap has " << myMap.size() << " elements" << endl;
  }
  
  // 删除元素,使用 erase 方法传入键
  myMap.erase(1); 
  
  return 0;
}

Valid allophones

This section corresponds to Code Random Notes: Code Random Notes , Explanatory Video: Learn Hash Tables thoroughly, and have tips for using arrays! Leetcode: 242. Valid allophones_bilibili_bilibili

exercise

Question link: 242. Valid anagrams - LeetCode

Given two strings s and t, write a function to determine whether t is an anagram of s.
Note: If the number of occurrences of each character in s and t is the same, then s and t are said to be anagrams of each other, and s and t only contain lowercase letters.

Example 1:
Input: s = “anagram”, t = “nagaram”
Output: true

Example 2:
Input: s = “rat”, t = “car”
Output: false

Advanced: What if the input string contains unicode characters? Can you adapt your solution to handle this situation?

sort

First sort the two strings, and then you can directly compare whether the two strings are equal.

class Solution {
    
    
public:
    bool isAnagram(string s, string t) {
    
    
        if (s.length() != t.length()) {
    
    
            return false;
        }
        sort(s.begin(), s.end());
        sort(t.begin(), t.end());
        return s == t;
    }
};
  • Time complexity: O(nlogn). Where n is the length of the string. Because the sorting function is used, the time complexity of sorting is O(nlogn). The time complexity of string comparison is O(n), so the total time complexity is O(nlogn + n) = O(nlogn).
  • Space complexity: O(log n). Where n is the length of the string. Sorting requires O(logn) stack space, so the space complexity is O(logn).

My solution

Since both s and t contain only lowercase letters, you can define two arrays respectively to store the number of occurrences of the corresponding characters. If the values ​​at the corresponding positions in the two arrays are the same, it is true. At the same time, if the two strings have different lengths, you can directly return false.

class Solution {
    
    
   public:
    bool isAnagram(string s, string t) {
    
    
        // 两个字符若长度不等直接返回false
        int size = s.length();
        if (size != t.length()) {
    
    
            return false;
        }
        int num_s[26] = {
    
    0};
        int num_t[26] = {
    
    0};
        // 遍历字符串,两个计数的数组计数
        for (int i = 0; i < size; i++) {
    
    
            num_s[int(s[i]) - 97] += 1;
            num_t[int(t[i]) - 97] += 1;
        }
        // 一旦某一个字符的次数不一样直接返回false
        for (int i = 0; i < 26; i++) {
    
    
            if (num_s[i] != num_t[i]) {
    
    
                return false;
            }
        }
        return true;
    }
};
  • Time complexity: O(n). Where n is the length of the string. Traversing the string takes O(n) time, and traversing the count array takes O(1) time, so the total time complexity is O(n).
  • Space complexity: O(1). Since only count arrays of length 26 are used, the space complexity is constant.

Points that can be optimized

  • int(s[i]) - 97Can be replaced by s[i] - 'a', so there is no need to remember the ASCII of character a

Hash table

In fact, my solution also belongs to a hash table, but in fact, I don’t need two arrays to count. I only need one array to count one of the strings, and then when traversing the other string, the value of the corresponding position is reduced by 1. If the value of the corresponding position is < 0, indicating that there are more elements at this position of t than that of s, then return false

For example, s is aab and t is aaa. When traversing to the second a of t, table[0] is already equal to 0. When traversing to the third a of t, table[0]=-1<0, indicating that a in t is smaller than a in s. There is at least one more a (there may be more a later, but there is no need to traverse it again).

In Code Capriccio, t is first traversed, the corresponding position is decremented by 1, and then it is judged whether the array is all 0, but the following solution only requires two for loops, which is better than the three for loops in Code Capriccio.

class Solution {
    
    
public:
    bool isAnagram(string s, string t) {
    
    
        if (s.length() != t.length()) {
    
    
            return false;
        }
        int table[26] = {
    
    0};
        // 数组计数
         for (int i = 0; i < s.length(); i++) {
    
    
            table[s[i] - 'a']++;
        }
        for (int i = 0; i < t.length(); i++) {
    
    
            // 遍历字符串t对应位置减1
            table[t[i] - 'a']--;
            // 如果<0说明这个位置t的比s的多,即多减了一次
            if (table[t[i] - 'a'] < 0) {
    
    
                return false;
            }
        }
        return true;
    }
};
  • Time complexity: O(n). Where n is the length of the string, it requires traversing two strings and performing a constant number of array operations.
  • Space complexity: O(1). Because an array of size 26 is used to store the number of occurrences of each letter, the space size has nothing to do with the length of the input string.

Advanced solution

If the input string contains unicode characters, then the possible characters are no longer 26 and cannot be counted using an array of size 26. At this point you need to use unordered_map.

The idea is similar to the hash table above. First traverse s and put each element into the hash table. Then traverse the string t. If the number of an element <0 means that there are more elements at this position in t than in s, then false is returned. It’s just that the hash table solution count is based on the character -a to find the index, and this one is based on the key to find the index directly

class Solution {
    
    
   public:
    bool isAnagram(string s, string t) {
    
    
        if (s.size() != t.size()) {
    
      // 先判断两个字符串长度是否相同
            return false;
        }
        unordered_map<char, int> hash;  // 定义哈希表

        for (int i = 0; i < s.size(); i++) {
    
      // 遍历一个字符串
            hash[s[i]]++;  // 把s的每个字符放进哈希表中
        }                  
        for (int i = 0; i < t.size(); i++) {
    
    
            hash[t[i]]--;  // 在hash表中抵消该字符次数
            if (hash[t[i]] < 0) {
    
    
                return false;
            }
        }
        return true;  // 若出现次数都一致,则返回true
    }
};
  • Time complexity: O(n). Where n is the length of the string, you need to traverse the two strings and perform a constant number of operations on the hash table.
  • Space complexity: O(n). Because a maximum of n characters can be stored in a hash table, the space complexity is related to the length of the input string.

Intersection of two arrays

This section corresponds to Code Random Notes: Code Random Notes , explanation video: Learn the hash table thoroughly, and have tips for using set! Leetcode: 349. Intersection of two arrays_bilibili_bilibili

exercise

Question link: 349. Intersection of two arrays - LeetCode

Given two arrays nums1 and nums2, return their intersection. Each element in the output must be unique. We can ignore the order of output results.

Example 1:
Input: nums1 = [1,2,2,1], nums2 = [2,2]
Output: [2]

Example 2:
Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
Output: [9,4]
Explanation: [4,9] is also passable

My solution

For this question, I used the unordered_map used in the advanced solution to the previous question, but because of the intersection, if there are duplicates, they can only be output once. So when I save it, I make the times corresponding to nums1 be 1. When traversing nums2, if the number is 1, push_back and the number +1, so that there will be no repeated output.

class Solution {
    
    
   public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
    
        unordered_map<int, int> hash;
        vector<int> res;
        // 不同的数只放一次
        for (int i = 0; i < nums1.size(); i++) {
    
    
            hash[nums1[i]] = 1;
        }
        for (int i = 0; i < nums2.size(); i++) {
    
    
            // 次数为1说明重叠,加1后后面就不会push_back同样的元素
            if (hash[nums2[i]] == 1) {
    
    
                res.push_back(nums2[i]);
                hash[nums2[i]]++;
            }
        }
        return res;
    }
};
  • Time complexity: O(m+n). Where m is the length of nums1 and n is the length of nums2. It is necessary to traverse two arrays to build a hash table and find overlapping elements, and the time complexity is O(m+n).
  • Space complexity: O(min(m,n)). A maximum of min(m,n) different elements can be stored in a hash table, so the space complexity is O(min(m,n)).

set solution

This question is due to the elimination of duplication, so it is actually more appropriate to use set.

Two sets are used in the code caprice, one traverses nums1, and the other stores the results (so that the results will be deduplicated). But in fact, only one set is needed. After traversing nums1 with a set, when traversing nums2, determine whether the element appears. If it appears, push_back and erase to delete the element. For example, even if there is a repeated 2 in Example 1, we delete the 2 in the set after traversing the first 2 and performing push_back. When we encounter the second 2, it is actually in a state where it cannot be found, so There will be no duplicate output.

class Solution {
    
    
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
    
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        vector<int> res;
        for (int num : nums2) {
    
    
            // 如果nums_set包含num,则num是交集之一,加入结果并从集合中删除num
            if (nums_set.erase(num)) {
    
    
                res.push_back(num);
            }
        }
        return res;
    }
};
  • Time complexity: O(m+n). where m and n are the lengths of nums1 and nums2 respectively. Because nums1 and nums2 need to be traversed once each, and the time complexity of the search and delete operations of unordered_set is constant, the total time complexity is O(m+n).
  • Space complexity: O(min(m,n)). where m and n are the lengths of nums1 and nums2 respectively. Because you need to use unordered_set to store the elements in nums1, the space complexity depends on the smaller of nums1 and nums2.

happy number

This section corresponds to the Code Random Record: Code Random Record , explanation video: None yet

exercise

Question link: 202. Happy Number - LeetCode

Write an algorithm to determine whether a number n is a happy number.

"Happy Number" is defined as:

  • For a positive integer, each time the number is replaced by the sum of the squares of its digits at each position.
  • Then repeat this process until the number reaches 1, or it may loop infinitely but never reaches 1.
  • If the result of this process is 1, then this number is the happy number.
  • Returns true if n is a happy number; otherwise, returns false.

Example 1:
Input: n = 19 Output: true
Explanation:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

set solution

After I first saw the title, I have been struggling with what is said in the title.Possibly infinite loop. It is not difficult to split the number and determine whether the sum is 1, but if the program may fall into an infinite loop, it will time out if the result is not found, but the sum is never 1, and it is impossible to determine whether the subsequent sum will be 1.

When you have no idea, you can output the sum of each number to see the whole process. The code is as follows

int n = 12;
for (int i = 0; i < 20; i++) {
    
    
    int sum = 0;
    while (n != 0) {
    
    
        sum += (n % 10) * (n % 10);
        n /= 10;
    }
    cout << sum << "#";
    n = sum;
}

The sum of the numbers will be output each time, a total of 20 times.

When n=12, you will lose 5#25#29#85#89#145#42#20#4#16#37#58#89#145#42#20#4#16#37#58#. Pay attention to the looping of the numbers. #89#145#42#20#4#16#37#58#This cycle has been going on since 1989. Since there is a loop, it means that when the calculated sum is 89, the program will return to 89 after calculating the sum for a period of time. In other words, if a number appears for the second time, then there must be a loop before the number, and the number n must not be a happy number. Therefore, it can be unordered_setused to determine whether the element appears repeatedly. If it appears repeatedly, false can be returned , terminate this infinite loop.

class Solution {
    
    
   public:
    bool isHappy(int n) {
    
    
        unordered_set<int> mySet;
        while (1) {
    
    
            int sum = 0;
            // 计算每个位置上的数字的平方和
            while (n != 0) {
    
    
                sum += (n % 10) * (n % 10);
                n /= 10;
            }
            // 和为1则说明是快乐数
            if (sum == 1) {
    
    
                return true;
            }
            // 如果set中存在sum,说明一定会陷入循环中,即不可能是快乐数
            if (mySet.count(sum)) {
    
    
                return false;
            }
            mySet.insert(sum);
            n = sum;    // 将sum赋值给n,继续计算数字n每个位置上的数字的平方和
        }
    }
};
  • Time complexity: O(logn). The cost of finding the next value of a given number is O(logn)
  • Space complexity: O(k). Among them, k is the number of loops, and each loop will be stored in unordered_set.

sum of two numbers

This section corresponds to Code Random Notes: Code Random Notes , explanation video: Where the Dream Begins, Leetcode: 1. The sum of two numbers, learn the hash table thoroughly, and have tips for using map! _bilibili_bilibili

exercise

Question link: 1. Sum of two numbers - LeetCode

Given an integer array nums and an integer target value target, please find the two integers in the array whose sum is the target value target and return their array subscripts.
You can assume that each input will correspond to only one answer. However, the same element in the array cannot appear repeatedly in the answer.
You can return answers in any order, and there will only be one valid answer

Example 1:
Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Explanation: Because nums[0] + nums[1] == 9, return [0, 1]

Example 2:
Input: nums = [3,2,4], target = 6
Output: [1,2]

Example 3:
Input: nums = [3,3], target = 6
Output: [0,1]

brute force solution

As the first question of LeetCode, the meaning of the question is very clear. It is to find two numbers whose sum is target in the array, return their subscripts, and these two numbers must be found, and the answer is unique (the order can be different).

The most direct way is to use two for loops to traverse a number to see if there is any remaining number that satisfies the sum of target.

class Solution {
    
    
   public:
    vector<int> twoSum(vector<int>& nums, int target) {
    
    
        int size = nums.size();
        vector<int> res;
        for (int i = 0; i < size; i++) {
    
    
            for (int j = i + 1; j < size; j++) {
    
    
                if (nums[i] + nums[j] == target) {
    
    
                    res.push_back(i);
                    res.push_back(j);
                    return res;
                }
            }
        }
        return res;
    }
};
  • Time complexity: O(n^2). There are two loops, each loop runs n times, so the total number of runs is n * n = n^2
  • Space complexity: O(n). A vector res of length n is created to store the results

Points that can be optimized

  • In fact, there is no need to define a new res, just return directly when returning return {i,j}, so that the space complexity is optimized to O(1)

Hash table

The above brute force solution is to traverse a number first, and then check whether there is target-nums[i] in the remaining numbers. That simplifies it to the problem of finding whether an element exists in the array, so you can use a hash table, because the time complexity of finding an element in a hash table is O(1).

There are three types of hash tables: array, set and map. Only set and map have O(1) find function. However, set only stores keys, and the question requires that the subscript be returned. Set cannot directly obtain the subscript of the original number.

There is a distance function in STL that can calculate the distance between two elements. Can the subscript be obtained by calculating the distance between the found element and set.begin()?
Answer: This method cannot obtain the original subscript. When inserting elements using unordered_set, the original order will not be retained, but will be randomly sorted according to the internal order of the elements in the hash table. That is, even if you add the same elements to the set as in the vector, their order in the set may be different from the order in v1. For example, {2,7,11,15} in the example will become the order of {11,7,15,2} in unordered_set

Among the maps, only unordered_map has a query efficiency of O(1), so what are the keys and values ​​stored in? When searching in map, the key is searched, and what we are looking for is target-nums[i], that is, the element itself is searched. Then the key stores the elements in the array, and the value stores the corresponding subscripts.

My writing is as follows

class Solution {
    
    
   public:
    vector<int> twoSum(vector<int>& nums, int target) {
    
    
        // 创建一个无序哈希表map,key为整数型,value为整数型
        unordered_map<int, int> map;
        // 遍历nums中的元素,将元素值作为键,元素下标作为值插入到map中
        for (int i = 0; i < nums.size(); i++) {
    
    
            map.insert({
    
    nums[i], i});
        }
        for (int i = 0; i < nums.size(); i++) {
    
    
            int tmp = target - nums[i];
            // 判断map中是否含有该差值,如果存在且满足条件则返回下标值
            if (map.find(tmp) != map.end()) {
    
    
                if (i != map[tmp]) {
    
    
                    return {
    
    i, map[tmp]};
                }
            }
        }
        return {
    
    };
    }
};
  • Time complexity: O(n). Among them, the first for loop traverses the entire nums array, requiring n operations; the second for loop also traverses the entire nums array, requiring n operations, and the hash table map inserts n elements, requiring n operations. Therefore, the total time complexity is O(n+n+n)=O(n)
  • Space complexity: O(n). A hash table is created and n elements are stored, so the space complexity is O(n)

After reading the solution, the above writing method can be optimized. I inserted all the elements into the map first, but there is actually no need to insert all the elements first. You only need to insert the current nums[i] into the map when there is no target - nums[i]. This also avoids matching with itself, that is, 2*nums[i]=target. For example, 2,7,11,15, target=9, when i=0, although 7 is not found in the map. But after inserting 2 into the map, when i=1, you can find whether the map contains 2.

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 it = map.find(target - nums[i]);
            if (it != map.end()) {
    
    
                // 如果找到了,返回这两个元素的下标
                return {
    
    it->second, i};
            }
            // 否则将当前的数插入哈希表
            map[nums[i]] = i;
        }
        return {
    
    };
    }
};
  • Time complexity: O(n). where n is the number of elements in the nums array, because you only need to traverse the nums array once to find two elements that meet the conditions of the target value.
  • Space complexity: O(n). In the worst case, all elements need to be inserted into the hash table, so the space occupied by the hash table is the same as the size of the nums array.

Adding Four Numbers II

This section corresponds to the Code Random Notes: Code Random Notes , explanation video: Learn the hash table thoroughly, and have tips for using map! LeetCode: 454. Addition of Four Numbers II_bilibili_bilibili

exercise

Question link: 454. Addition of Four Numbers II - LeetCode

You are given four integer arrays nums1, nums2, nums3 and nums4. The array lengths are all n. Please calculate how many tuples (i, j, k, l) can satisfy:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
    Example 1:
    Input: nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1 ,2], nums4 = [0,2]
    Output: 2
    Explanation: The two tuples are as follows:
    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

brute force solution

The violent solution will time out, this is only for reference

Simply put, the meaning of the question is to find a number in each of the four arrays, so that their sum is 0. The most straightforward is to use four for loops to traverse each array. As long as the sum is 0, add 1. But the time complexity of the quadruple for loop is O(n^4), and it will definitely time out.

So can we reduce the quadruple for loop to a triple for loop?

What we are learning in this chapter is hash tables, and the time complexity of finding whether a specified element appears in a hash table is O(1). The fourth level of for loop is actually to find whether the number 0-nums1-nums2-nums3 exists. If it exists, what is its number. Then we can use an unordered_map to store an array such as nums4, the key stores the elements of nums4, and the value stores the number of occurrences. This can reduce the time complexity to O(n^3), but even if it is optimized to a triple for loop, it will still time out.

Note that set cannot be used. Set can only store keys, that is, it cannot count the number of occurrences. Although using multiset and then counting using the count function can also count the times, the search time complexity of multiset is O(logn).

class Solution {
    
    
   public:
    int fourSumCount(vector<int>& nums1,vector<int>& nums2,vector<int>& nums3,vector<int>& nums4) {
    
    
        int size = nums1.size();
        int res = 0;
        unordered_map<int, int> hash_map;
        // key存放nums4的元素,value存放出现的次数
        for (int i = 0; i < size; i++) {
    
    
            hash_map[nums4[i]]++;
        }
        for (int i = 0; i < size; i++) {
    
    
            for (int j = 0; j < size; j++) {
    
    
                for (int m = 0; m < size; m++) {
    
    
                    // 如果能在hash_map中找到-(nums1+nums2+nums3),则将res+出现的次数
                    auto it = hash_map.find(0 - nums1[i] - nums2[j] - nums3[m]);
                    if (it != hash_map.end()) {
    
    
                        res += it->second;
                    }
                }
            }
        }
        return res;
    }
};
  • Time complexity: O(n^3). where n is the length of the nums1, nums2, nums3, nums4 arrays, because there are three nested for loops
  • Space complexity: O(n). Because a hash table is used to store the elements of nums4 and their occurrence times, and the size of the hash table is at most n

Hash table

Since the above triple for loop will time out, the solution must be smaller than the triple for loop. For example, can we just use two for loops, which is O(n^2)

In the above solution, we use map to store the elements of an array, and then traverse the remaining three arrays. So if we first traverse two arrays, let map store their sum, and then traverse the other two arrays. You can find whether the map contains -(nums3+nums4), and if so, add its number. This way we can continue to optimize to O(n^2).

In the same way, if there are six arrays, then you can store the sum of three arrays, and then triple the for loop, that is, store the sum of half of the arrays.

class Solution {
    
    
   public:
   int fourSumCount(vector<int>& nums1,vector<int>& nums2,vector<int>& nums3,vector<int>& nums4) {
    
    
        int size = nums1.size();
        int res = 0;
        unordered_map<int, int> hash_map;
        // key存放nums1+nums2的和,value存放和出现的次数
        for (int i = 0; i < size; i++) {
    
    
            for (int j = 0; j < size; j++) {
    
    
                hash_map[nums1[i] + nums2[j]]++;
            }
        }
        for (int i = 0; i < size; i++) {
    
    
            for (int j = 0; j < size; j++) {
    
    
                // 如果能在hash_map中找到-(nums3+nums4),则将res+出现的次数
                auto it = hash_map.find(0 - nums3[i] - nums4[j]);
                if (it != hash_map.end()) {
    
    
                    res += it->second;
                }
            }
        }
        return res;
    }
};
  • Time complexity: O(n^2). n is the length of the nums1, nums2, nums3, and nums4 arrays. Because a two-level for loop is used to traverse the four arrays, each operation takes O(1) time. There is also a search operation on the hash table. The time complexity of the search is O(1), so the total time complexity is O(n^2)
  • Space complexity: O(n^2). n is the length of the nums1, nums2, nums3, and nums4 arrays. We use a hash table to record the pairwise sum of all elements in nums1 and nums2. There are at most n^2 different possibilities, so the space complexity is O( n^2)

ransom letter

This section corresponds to the Code Random Record: Code Random Record , explanation video: None yet

exercise

Question link: 383. Ransom letter - LeetCode

Given two strings: ransomNote and magazine, determine whether ransomNote can be composed of the characters in magazine. Returns true if possible; otherwise returns false.

Each character in magazine can only be used once in ransomNote.

Example 1:
Input: ransomNote = “aa”, magazine = “ab”
Output: false

Example 2:
Input: ransomNote = “aa”, magazine = “aab”
Output: true

hint:

  • 1 <= ransomNote.length, magazine.length <= 105
  • ransomNote and magazine are composed of lowercase English letters

Hash table

This simple question is similar to the previous question on valid anagrams. It is the fastest question to write.

To determine whether ransomNote can be composed of magazine, you only need to traverse the magazine first and save the number of occurrences of each character, and then traverse ransomNote. When encountering each character, count it by -1. If the count is less than 0, it means that this character in ransomNote At least one more than magazine, that is, it cannot be composed of magazine. And if after traversing the ransomNote, no one with a count less than 0 can be found, it means that it can be composed of magazines.

To find elements, you must use a hash table. The key to solving the problem is to choose which one of array, set, and map is better? First of all, set can only store keys, and we also need to count, which is definitely inappropriate. The title says that ransomNote and magazine are composed of lowercase English letters, which means that there are up to 26 characters, so it is more appropriate to use an array of 26. Of course, you can also use map, but map is more time-consuming to maintain hash tables than arrays, so arrays are better. I will give the code and comparison of the two solutions later.

The first is the solution to the array

class Solution {
    
    
   public:
    bool canConstruct(string ransomNote, string magazine) {
    
    
        int nums[26] = {
    
    0};
        // 先遍历magazine计算每个字符的出现次数
        for (int i = 0; i < magazine.length(); i++) {
    
    
            nums[magazine[i] - 'a']++;  // 根据-'a'计算相对位置
        }
        // 再遍历ransomNote
        for (int i = 0; i < ransomNote.length(); i++) {
    
    
            // 先将次数-1,这样如果<0(0-1<0)说明比magazine至少多一个
            nums[ransomNote[i] - 'a']--;
            if (nums[ransomNote[i] - 'a'] < 0) {
    
    
                return false;
            }
        }
        return true;
    }
};
  • Time complexity: O(n). where N is the length of the magazine, because traversing the magazine can take O(N) time, and for each character, the nums array can be updated in constant time. Then we traverse randomNote again, there are N characters. For each character, we need constant time to check whether the frequency of the current character exists in nums, so the total time complexity is O(N)
  • Space complexity: O(1). Because the size of the nums array is always constant (26)

Optimization: You can add a judgment at the front. If randomNote is longer than magazine, it must not be composed of magazine, and return false.

The solution to map is as follows

class Solution {
    
    
   public:
    bool canConstruct(string ransomNote, string magazine) {
    
    
        unordered_map<char, int> hash_map;
        // 先遍历magazine计算每个字符的出现次数
        for (int i = 0; i < magazine.length(); i++) {
    
    
            hash_map[magazine[i]]++;    // 直接将相应元素做为key
        }
        // 再遍历ransomNote
        for (int i = 0; i < ransomNote.length(); i++) {
    
    
            // 先将次数-1,这样如果<0(0-1<0)说明比magazine至少多一个
            hash_map[ransomNote[i]]--;
            if (hash_map[ransomNote[i]] < 0) {
    
    
                return false;
            }      
        }
        return true;
    }
};
  • Time complexity: O(n). where n is the length of magazine plus the length of randomNote, because the code contains two for loops, each loop is executed no more than the length of the input string
  • Space complexity: O(m). where m is the number of different characters in magazine. In the worst case, the hash table will contain all characters in magazine

It can be seen that the two solutions actually have different keys. The array uses the relative position as the key, while the map directly uses the corresponding element as the key. The following are the submission records of the two solutions of LeetCode. It can also be seen from the figure that the array solution is better.

sum of three numbers

This section corresponds to Code Random Notes: Code Random Notes , explanation video: The place where dreams are broken! | LeetCode: 15. Sum of three numbers_bilibili_bilibili

exercise

Question link: 15. Sum of three numbers - LeetCode

Given an integer array nums, determine whether there is a triplet [nums[i], nums[j], nums[k]] that satisfies i != j, i != k and j != k, and also satisfies nums[ i] + nums[j] + nums[k] == 0 . please

You return all non-duplicate triples that sum to 0.

Note: Answers cannot contain duplicate triples.
Example 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] 。
注意,输出的顺序和三元组的顺序并不重要。

double pointer

The difficulty of this question is mainly "non-repeatable". In this case, if the hash table is used directly, there will be duplication, but the operation of removing duplicates is very troublesome. It's actually more appropriate to use double pointers. When I was writing, I also thought of sorting first and then deduplicating the first number, but I never figured out how to deduplicate the second and third numbers. In fact, because the topic of this chapter is a hash table, I have been thinking about using a hash table to solve it.

Find abc in the array such that a + b +c =0, which is equivalent to a = nums[i], b = nums[left], c = nums[right]

Insert image description here

Why sort first? As shown in the animation, after sorting, the array will be in order. For example, when a is deduplicated, when i is the second -1, it is the same as the previous -1, so it can be skipped directly nums[i] == nums[i - 1]. Note that i and i+1 cannot be used for comparison here, because the triplet can be repeated internally, such as -1 -1 2.

For the i pointer, it is the first number of the traversed triplet. Using the above method, a can be deduplicated. So how to choose the remaining two numbers?

If nums[i] + nums[left] + nums[right] > 0, it means that the sum of the three numbers is large at this time. Because the array is sorted, the right subscript should be moved to the left, so that the three numbers can be The sum is smaller. If nums[i] + nums[left] + nums[right] < 0, it means that the sum of the three numbers is small at this time, and left will move to the right to make the sum of the three numbers larger until left and right meet. When left=right, the second number and the third number are the same number, which does not conform to the meaning of the question, so the boundary condition is right > left.

For b and c deduplication, when a suitable 3-tuple is found, if , nums[right] == nums[right - 1]let the right remain –, because the array is ordered, and the deduplication operation can be completed until a non-duplicate number is found. In the same way, left always ++

class Solution {
    
    
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
    
    
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
    
    
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) {
    
    
                return result;
            }
            // 错误去重a方法,将会漏掉-1,-1,2 这种情况
            /*
            if (nums[i] == nums[i + 1]) {
                continue;
            }
            */
            // 正确去重a方法
            if (i > 0 && nums[i] == nums[i - 1]) {
    
    
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
    
    
                // 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
                /*
                while (right > left && nums[right] == nums[right - 1]) right--;
                while (right > left && nums[left] == nums[left + 1]) left++;
                */
                if (nums[i] + nums[left] + nums[right] > 0) right--;
                else if (nums[i] + nums[left] + nums[right] < 0) left++;
                else {
    
    
                    result.push_back(vector<int>{
    
    nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;

                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }

        }
        return result;
    }
};
  • Time complexity: O(n^2). Where n is the size of the input array nums. Mainly in the outer for loop and inner while loop. Since the for loop traverses each element and the inner while loop moves double pointers, the two loops are nested and the time complexity is O(n^2)
  • Space complexity: O(n). Mainly in the storage result vector<vector> result, the maximum size of the two-dimensional vector is C(n,3), that is, the number of combinations of three out of n elements. Therefore the space complexity is O(n)

sum of four numbers

This section corresponds to Code Random Notes: Code Random Notes , explanation video: The difficulty lies in removing duplicates and pruning! | LeetCode: 18. The sum of four numbers_bilibili_bilibili

exercise

Question link: 18. Sum of four numbers - LeetCode

You are given an array nums consisting of n integers, and a target value target. Please find and return the non-duplicate quadruples [nums[a], nums[b], nums[c], nums[d]] that meet all the following conditions (if the two quadruple elements correspond one-to-one , then the two quadruples are considered repeated):

0 <= a, b, c, d < n
a, b, c and d are different from each other
nums[a] + nums[b] + nums[c] + nums[d] == target
You can return in any order Answer.

示例 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]]

double pointer

The solution to this problem is similar to the sum of three numbers above. In the sum of three numbers, we use an i pointer to traverse the first element, and then use the left and right pointers to find the appropriate second and third elements. In this question, we still use the left and right pointers to find the appropriate last two elements, but we need to add an extra layer of for loop to traverse the second element.

For the deduplication of the first element, our judgment condition is i>0&&nums[i]==nums[i-1], and for the deduplication of the second element, it is nums[j]==nums[j-1]still only here. It j>i+1cannot be written j>0, such as 0 0 0 0, target =0, i=0, j=1 nums[j]==nums[j-1]. In fact, nums[j] is used to compare with the previous nums[i]. When j=i+1, it is possible to satisfy the condition of the quadruple. Therefore, the judgment condition is j>i+1&&nums[j]==nums[j-1], in fact, it is greater than the initial value of its cycle.

In addition, it should be noted that if four numbers are added directly, there will be an integer overflow in the test case given by LeetCode, so it must be converted to long. You can write four assignment statements to convert all four numbers into long, but a better way is to convert one of the numbers into long when adding, and the remaining int type elements will be automatically upgraded to long type for calculation. This avoids the problem of integer overflow

For the same reason, the sum of five numbers, the sum of six numbers, etc. all use this solution.

class Solution {
    
    
   public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
    
    
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c + d = target
        // a = nums[i], b = nums[j],b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
    
    
            // 对a去重
            if (i > 0 && nums[i] == nums[i - 1]) {
    
    
                continue;
            }
            for (int j = i + 1; j < nums.size(); j++) {
    
    
                // 对b去重
                if (j > i + 1 && nums[j] == nums[j - 1]) {
    
    
                    continue;
                }
                int left = j + 1;
                int right = nums.size() - 1;
                while (right > left) {
    
    
                    // nums[i] + nums[j] + nums[left] + nums[right] > target 会溢出
                    if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target) {
    
    
                        right--;
                        // nums[i] + nums[j] + nums[left] + nums[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]});
                        // 去重逻辑应该放在找到一个四元组之后,对b和c去重
                        while (right > left && nums[right] == nums[right - 1])
                            right--;
                        while (right > left && nums[left] == nums[left + 1])
                            left++;

                        // 找到答案时,双指针同时收缩
                        right--;
                        left++;
                    }
                }
            }
        }
        return result;
    }
};
  • Time complexity: O( n 3 n^3n3 ). There are two for loops in the outer layer, and the time complexity is O(n 2 n^2n2 ), a while loop is used internally, the time complexity is O(n), and the final total time complexity is O(n 2 ∗ nn^2 * nn2n) = O( n 3 n^3 n3)。
  • Space complexity: O(n). Only one vector<\vector> result is opened to store the answer, so the space complexity is O(n)

Guess you like

Origin blog.csdn.net/zss192/article/details/129804411