LeetCode brush questions (ACM mode)-03 hash table

References: code thoughts

  • Note: Each LeetCode question uses the ACM code mode, which can be run directly locally, and the blue font is the hyperlink of the question

0. Theoretical basis of hash table

0.1 hash table

  • Hash table ( Hash table, also known as hash table ) is a data structure that is directly accessed according to the value of the key code . Generally speaking, an array is a hash table, and the key code in the hash table is the index subscript of the array. , and then directly access the elements in the array through the subscript

insert image description here

  • So what problems can hash tables solve? Generally, hash tables are used to quickly determine whether an element appears in the set
    • For example, to check whether a name is in this school. The time complexity of enumeration is O(n), while the hash table only needs O(1): only use initialization to store the names of the students in this school in the hash table , and you can directly know them through the index when querying Is this classmate in this school

0.2 Hash function

  • Hash function, directly maps the student's name to the index on the hash table , and then query the index subscript to quickly know whether the student is in this school

  • The hash function is shown in the figure below. The name is converted into a value through hashCode . Generally, hashcode can convert other data formats into different values ​​through a specific encoding method, so that student's name is mapped to the index number on the hash table.

insert image description here

  • What should I do if the value obtained by hashCode is larger than the size tableSize of the hash table?

    • In order to ensure that the mapped index values ​​fall on the hash table, a modulo operation will be performed on the values ​​again , so as to ensure that the student names can be mapped to the hash table
  • At this point the question comes again, what if the number of students is greater than the size of the hash table? At this time, even if the hash function is calculated evenly, it is inevitable that the names of several students will be mapped to the same index subscript position of the hash table at the same time, which is called hash collision.

0.3 Hash Collision

  • As shown in the figure, both Xiao Li and Xiao Wang are mapped to the position of index subscript 1. This phenomenon is called hash collision

insert image description here

0.3.1 Hash collision solution: zipper method and linear detection method
  • zipper method
    • Xiao Li and Xiao Wang have a conflict at index 1, and the conflicting elements are stored in the linked list, so that they can be found through the index
    • In fact, the zipper method is to choose the appropriate size of the hash table , so that it will not waste a lot of memory due to the empty value of the array, nor will it waste too much time on the lookup because the linked list is too long (the data size is dataSize, the hash table the size of the tableSize)
      insert image description here
  • linear probing
    • When using the linear detection method, be sure to ensure that tableSize > dataSize. It is necessary to rely on the vacancy in the hash table to solve the collision problem: for example, if Xiao Li is placed in the conflicting position, then find a vacancy down to place Xiao Wang's information . Therefore, tableSize must be greater than dataSize, otherwise there will be no space in the hash table to store conflicting data. as the picture shows
      insert image description here

0.4 Three common hash structures

  • Array, set (collection), map (mapping)

  • In C++, set and map respectively provide the following three data structures, and their underlying implementations and advantages and disadvantages are shown in the following table

    • The underlying implementation of std::unordered_set is a hash table, and the underlying implementation of std::set and std::multiset is a red-black tree, which 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 the whole tree to be confused, so it can only be added or deleted.
      insert image description here

    • 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. Similarly, the keys of std::map and std::multimap are also ordered (this question is often used as an interview question to examine the understanding of the underlying language container)
      insert image description here

  • When using a set to solve the hash problem, use unordered_set first, because its query and addition and deletion efficiency are optimal. If the set is required to be ordered, then use set. If not only order but also repetition are required For data, then use multiset; while map is a key value data structure , there are restrictions on keys in map, but no restrictions on value, because the storage method of keys is realized by using red-black tree

Summarize

  • Although the underlying implementations of std::set and std::multiset are red-black trees, and std::set and std::multiset use red-black trees for indexing and storage , they still use hashing methods, namely key and value. So using these data structures to solve the mapping problem is still called hashing, and map is the same
  • When it comes to quickly judging whether an element appears in a collection, hashing must be considered. But the hash method also sacrifices space for time, because an additional array set or map is used to store data in order to achieve fast search

1. Valid anagrams

242. Valid anagrams
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

  • Example 1
    Input: head = [1,2,6,3,4,5,6], val = 6 Output: [1,2,3,4,5]
  • Example 2
    Input: s = “rat”, t = “car”
    Output: false
  • Tip
    1 <= s.length, t.length <= 5 * 104
    You can assume the strings s and t contain only lowercase letters

1.1 Ideas

  • An array is actually a simple hash table, and the strings in this question only have lowercase characters, so you can define an array to record the number of occurrences of characters in the string s . How big an array needs to be defined? Define an array record with a size of 26 and initialize it to 0 , because the ASCII of a~z is also 26 consecutive values. For the sake of example, judge the string s = "aee", t = "eae", the animation is as follows
    insert image description here

1.1.1. Define an array record to record the number of occurrences of characters in the string s

  • Characters need to be mapped to the array, that is, the index subscript of the hash table, because the ASCII characters of a~z are 26 consecutive values, so the character a is mapped to subscript 0, and the corresponding character z is mapped to subscript 25
  • When traversing the string s again, you only need to perform +1 operation on the element where s[i] - 'a' is located, and you don't need to remember the ASCII of the character a, just ask for a relative value. In this way, the number of occurrences of characters in the string s can be counted

1.1.2. Check if these characters appear in the string t

  • Similarly, when traversing the string t, perform -1 operation on the value on the index of the character mapping hash table appearing in t

1.1.3. Check the record array if any element is not 0

  • Indicates that the strings s and t must have more characters or who has fewer characters, return false

1.1.4. If all elements of the record array are 0

  • Explain that the strings s and t are anagrams, return true

1.2 Code implementation

// 时间复杂度为:O(n)
// 空间度复杂度:O(1)
#include <iostream>
#include <string>
using namespace std;

class Solution {
    
    
public:
    bool isAnagram(string s, string t) {
    
    
        int record[26] = {
    
    0}; // 用来记录每个字符出现的次数,数组初始化为 0
        // 将字符减去'a'得到一个相对数值,然后将该相对数值作为 record 数组的下标
        // 将对应元素加 1,表示该字符出现了 1 次
        for (int i = 0; i < s.size(); i++) {
    
    
            // 并不需要记住字符 a 的ASCII,只要求出一个相对数值就可以
            record[s[i] - 'a']++;
        }
        // 方法同上,只不过将对应元素减 1,表示该字符出现了 1 次
        for (int i = 0; i < t.size(); i++) {
    
    
            record[t[i] - 'a']--;
        }
        // 遍历 record 数组,如果存在元素不为 0
            // 说明该元素对应的字符在字符串 s 和 t 中出现的次数不一致
            // 因此 s 和 t 不是字母异位词,return false
        for (int i = 0; i < 26; i++) {
    
    
            if (record[i] != 0) {
    
    
                return false;
            }
        }
        // record 数组所有元素都为零 0,说明字符串 s 和 t 是字母异位词
        return true;
    }
};

int main() {
    
    
    string s = "anagram";
    string t = "nagaram";
    Solution solution;
    cout << solution.isAnagram(s, t) << endl;
    return 0;
}

2. Intersection of two arrays

349. Intersection of Two Arrays
Given two arrays nums1 and nums2, return their intersection. Each element in the output result must be unique, regardless of the order of the output result

  • 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
  • 提示
    1 <= nums1.length, nums2.length <= 1000
    0 <= nums1[i], nums2[i] <= 1000

2.1 Ideas

  • If the hash value is relatively small, especially scattered, and the span is very large , using an array will cause a great waste of space. At this time, another structure set should be used

    • The underlying implementations of std::set and std::multiset are both red-black trees, and the underlying implementation of std::unordered_set is a hash table. Using unordered_set is the most efficient for reading and writing, and there is no need to sort the data, and do not let The data is repeated, so choose unordered_set
      insert image description here
  • But using set directly not only takes up more space than arrays, but also is slower than arrays . Sets need to do hash calculations to map values ​​to keys. According to the title, you can use arrays as hash tables, because the arrays are all 1000 Within

2.2 Code implementation

  • Use unordered_set as a hash table
// 时间复杂度: O(mn)
// 空间复杂度: O(n)
#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

class Solution {
    
    
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
    
        // 使用了无序集合来存储结果集 result_set,保证了结果集中不会有重复元素
        unordered_set<int> result_set; 
        // nums_set 使用了 nums1 容器中的元素来初始化
            // 包含了 nums1 中所有不重复的整数元素,且顺序不确定
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        // 范围 for 循环:依次取出 nums2 中的每个元素并将其赋值给循环变量 num
            // 判断它是否在 nums_set 中出现过,若是,则将它插入到 result_set 中
        for (int num : nums2) {
    
    
            // nums_set.find(num) 表示在 nums_set 集合容器中查找是否存在值为 num 的元素
            // 如果存在,则返回该元素的迭代器;否则,返回 nums_set.end(),表示未找到该元素
            // 因此 nums_set.find(num) != nums_set.end() 表示可以找到
            if (nums_set.find(num) != nums_set.end()) {
    
    
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

int main() {
    
    
    vector<int> nums1 = {
    
    1, 2, 2, 1};
    vector<int> nums2 = {
    
    2, 2};
    
    Solution solution;
    vector<int> result = solution.intersection(nums1, nums2);
    for (int num : result) {
    
    
        cout << num << " ";
    }
    cout << endl;
    
    return 0;
}
  • Use an array as a hash table
// 时间复杂度: O(m + n),其中 m 和 n 分别为两个输入数组的长度
// 空间复杂度: O(m + n),因为最坏情况下,两个输入数组没有任何公共元素,此时结果集大小为 0,result_set 占用空间为 O(m+n)
#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

class Solution {
    
    
public:
    vector<int> intersection(vector<int> &nums1, vector<int> &nums2) {
    
    
        // 使用了无序集合来存储结果集 result_set,保证了结果集中不会有重复元素
        unordered_set<int> result_set;
        int hash[1005] = {
    
    0};
        // 遍历 nums1 数组,将其中的每个元素作为下标
        // 在 hash 数组相应位置上标记为 1,表示该元素存在于 nums1 中
        for (int num : nums1) {
    
    
            hash[num] = 1;
        }
        // 遍历 nums2 数组中每个元素 num,判断其在 hash 数组中对应位置上的值是否为 1
        // 若是,则说明该元素同时存在于 nums1 和 nums2 中,并插入到 result_set 集合
        for (int num : nums2) {
    
    
            if (hash[num] == 1) {
    
    
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

int main(int argc, char *argv[]) {
    
    
    vector<int> nums1 = {
    
    1, 2, 2, 1};
    vector<int> nums2 = {
    
    2, 2};

    Solution solution;
    vector<int> result = solution.intersection(nums1, nums2);
    for (int num : result) {
    
    
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

3. Happy Number

202. Happy Number
Write an algorithm to judge whether a number n is a happy number.

  • Definition of happy number
    For a positive integer, replace the number with the sum of the squares of the numbers in each position and
    repeat this process until the number becomes 1, or it may be an infinite loop but it will never change to 1.
    If this process If the result is 1, then this number is a happy number.
    If n is a happy number, return true; otherwise, return false
  • Example 1
    Input: n = 19
    Output: true
    Explanation:
    1 2 + 9 2 = 82 1^2 + 9^2 = 8212+92=82
    8 2 + 2 2 = 68 8^2 + 2^2 = 68 82+22=68
    6 2 + 8 2 = 100 6^2 + 8^2 = 100 62+82=100
    1 2 + 0 2 + 0 2 = 1 1^2 + 0^2 + 0^2 = 1 12+02+02=1
  • Example 2
    Input: n = 2
    Output: false
  • 提示
    1 < = n < = 2 31 − 1 1 <= n <= 2^{31} - 1 1<=n<=2311

3.1 Ideas

  • The title says that it will be an infinite loop, that is to say, the sum will appear repeatedly during the summation process , so this question uses the hash method to determine whether the sum is repeated . If it is repeated, return false, otherwise it will find sum = 1 until the sum is judged. Whether repeated occurrences can use unordered_set

3.2 Code implementation

// 时间复杂度: O(logn)
// 空间复杂度: O(logn)
#include <iostream>
#include <unordered_set>

using namespace std;

class Solution {
    
    
public:
    // 计算 n 每个位上数字的平方和。具体实现:
        // 通过 while 循环不断将 n 除以 10 取余数
        // 然后计算这个余数的平方并加到 sum 中,继续将 n 除以 10
        // 直到 n 为 0 时停止,这样最终就能得到这个数每个位上数字的平方和
    int getSum(int n) {
    
    
        int sum = 0;
        while (n) {
    
    
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
    
    
        unordered_set<int> set; // 用来记录每次计算结果 sum,避免重复数字出现
        while (1) {
    
    
            int sum = getSum(n);
            // 如果等于 1,则说明这个数字是快乐数
            if (sum == 1) {
    
    
                return true;
            }
            // 判断 sum 是否已经在 set 中出现过
            // 如果出现过,则表明陷入了循环而不是得到了 1
            if (set.find(sum) != set.end()) {
    
    
                return false;
            } else {
    
    
                set.insert(sum);
            }
            n = sum; // 将计算结果 sum 赋值给变量 n,以便继续进行下一轮计算
        }
    }
};

int main(int argc, char *argv[]) {
    
    
    Solution solution;
    cout << solution.isHappy(19) << endl; // 输出 1
    cout << solution.isHappy(2) << endl; // 输出 0
    
    return 0;
}

4. The sum of two numbers

1. The sum of two numbers
Define an integer array nums and an integer target value target, please find the two integers whose sum is the target value target in the array, and return their array subscripts . You can assume that there is only one answer for each input. However, the same element in the array cannot appear repeatedly in the answer. You can return answers in any order

  • 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]
  • Tip
    2 <= nums.length <= 104
    -109 <= nums[i] <= 109
    -109 <= target <= 109
    There will only be one valid answer

4.1 Ideas

  • 1. When to use hashing

    • When you need to query whether an element has appeared, or whether an element is in a collection , you must first think of hashing. This question requires a collection to store the traversed elements, and then when traversing the array to query whether an element appears in this collection, then you should think of using the hash method
  • 2. Why use map for hash table

    • Because this question not only needs to know whether the element has been traversed, but also knows the subscript corresponding to the element . It needs to use the key value structure to store, the key to store the element, and the value to store the subscript of the element, so it is appropriate to use map . In addition, this The order of keys is not required in the question, so it is more efficient to choose std::unordered_map

    Limitations of using arrays or sets for hashing in this question

    • The size of the array is limited, and if there are few elements and the hash value is too large, memory space will be wasted
    • set is a collection, and the element in it can only be a key, and this question not only needs to judge whether y exists but also records the subscript position of y, because the subscripts of x and y need to be returned, so set cannot be used
  • 3. What is the map used for in this question?

    • map is used to store the visited elements, because when traversing the array, it is necessary to record which elements and corresponding subscripts have been traversed before, so as to find the elements and corresponding subscripts that match the current element
  • 4. What do the key and value in the map represent?

    • To judge whether an element appears, this element will be used as a key, so the element in the array is used as a key, and the key corresponds to the value, and the value is used to store the subscript
    • So the storage structure in the map is {key: data element, value: subscript corresponding to the data element}
    • When traversing the array, you only need to query the map to see if there is a value that matches the currently traversed element. If so, find the matching pair. If not, put the currently traversed element into the map, because the map stores the The elements visited , the process is as follows

insert image description here

insert image description here

4.2 Code implementation

// 时间复杂度: O(n)
// 空间复杂度: O(n)
#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

class Solution {
    
    
public:
    vector<int> twoSum(vector<int> &nums, int target) {
    
    
        // 定义无序映射(哈希表)map,用于存储每个元素及其下标
        unordered_map<int, int> map;
        // 遍历当前元素,并在 map 中寻找是否有匹配的 key
        for (int i = 0; i < nums.size(); ++i) {
    
    
            // 声明一个迭代器 iter,并指向 map 中等于 (target - nums[i]) 的元素
            auto iter = map.find(target - nums[i]);
            if (iter != map.end()) {
    
    
                return {
    
    iter->second, i}; // 如果找到,则返回这两个元素的下标
            }
            // 如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.insert(pair<int, int>(nums[i], i));
        }
        // 如果遍历完整个数组后仍未找到符合条件的两个整数,则返回一个空的整数向量
        return {
    
    }; 
    }
};

int main(int argc, char *argv[]) {
    
    
    vector<int> nums = {
    
    2, 7, 11, 15};
    int target = 9;

    Solution solution;
    vector<int> result = solution.twoSum(nums, target);
    cout << "[" << result[0] << ", " << result[1] << "]" << endl;
    
    return 0;
}

5. Add four numbers II

454. Four-number addition II
gives you four integer arrays A, B, C, and D, and the length of the arrays is n. Please calculate how many tuples (i, j, k, l) can satisfy

  • 0 <= i, j, k, l < n
  • A[i] + B[j] + C[k] + D[l] == 0
  • Example 1
    Input: A = [1,2], B = [-2,-1], C = [-1,2], D = [0,2]
    Output: 2
    Explanation:
    The two tuples are as follows:
    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
  • Example 2
    Input: A = [0], B = [0], C = [0], D = [0]
    Output: 1
  • 提示
    n == A.length == B.length == C.length == D.length
    1 <= n <= 200
    -228 <= A[i], B[i], C[i], D[i] <= 228

5.1 Ideas

  • This question is a classic question using the hash method, as long as you find A[i] + B[j] + C[k] + D[l] = 0, you don’t need to consider the case where the sum of four repeated elements is equal to 0 . Steps to solve this problem :
    • 1. First define an unordered_map, the key puts the sum of a and b, and the value puts the number of occurrences of the sum of a and b
    • 2. Traverse the big A and big B arrays, count the sum of the two array elements and the number of occurrences of the sum, and put them in the map
    • 3. Define the int variable count to count the number of occurrences of a+b+c+d = 0
    • 4. Then traverse the big C and big D arrays, find if 0-(c+d) has appeared in the map, use count to count the value corresponding to the key in the map, that is, the number of occurrences
    • 5. Finally, just return the statistical value count

5.2 Code implementation

// 时间复杂度: O(n^2)
// 空间复杂度: O(n^2),最坏情况下 A 和 B 的值各不相同,相加产生的数字个数为 n^2
#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

class Solution {
    
    
public:
    int fourSumCount(vector<int> &A, vector<int> &B, vector<int> &C, vector<int> &D) {
    
    
        unordered_map<int, int> umap;
        // 循环遍历 A 和 B 数组,将每个元素对的和添加到哈希表中,同时记录它们出现的次数
        for (int a : A) {
    
    
            for (int b : B) {
    
    
                umap[a + b]++; 
            }
        }
        // 遍历 C 和 D 数组,并在哈希表中查找是否存在等于 0-(c+d) 的键值
        int count = 0;
        for (int c : C) {
    
    
            for (int d : D) {
    
    
                if (umap.find(0 - (c + d)) != umap.end()) {
    
    
                    count += umap[0 - (c + d)]; // 如果存在,则将该元素的出现次数加入计数器中
                }
            }
        }
        return count;
    }
};

int main(int argc, char *argv[]) {
    
    
    vector<int> A = {
    
    1, 2};
    vector<int> B = {
    
    -2,-1};
    vector<int> C = {
    
    -1, 2};
    vector<int> D = {
    
    0, 2};

    Solution solution;
    int result = solution.fourSumCount(A, B, C, D);
    cout << result << endl;
    
    return 0;
}

6. Ransom letter

383. Ransom Note
Give you two strings: ransomNote and magazine, judge whether ransomNote can be composed of characters in magazine. Returns true if it can; otherwise returns false. In order not to expose the handwriting of the ransom letter, it is necessary to search for each required letter from the magazine to form words to express the meaning. Each character in magazine can only be used once in ransomNote

  • Example 1
    Input: ransomNote = "a", magazine = "b"
    Output: false
  • Example 2
    Input: ransomNote = "aa", magazine = "ab"
    Output: false
  • Example 3
    Input: ransomNote = "aa", magazine = "aab"
    Output: true
  • Tip
    1 <= ransomNote.length, magazine.length <= 105
    It can be assumed that ransomNote and magazine are only composed of lowercase letters

6.1 Ideas

  • This question judges whether the first string ransom can be composed of the characters in the second string magazines, but there are two points to note here
    • The first point is "in order not to expose the handwriting of the ransom letter, search for each required letter from the magazine to form a word to express the meaning" This shows that the letters in the magazine cannot be reused
    • The second point "You can assume that both strings contain only lowercase letters" means only lowercase letters
  • Because there are only lowercase letters, you can use the space-for-time hash strategy , use an array of length 26 to record the number of times letters appear in the magazine, and then use ransomNote to verify whether the array contains all the letters required by ransomNote . is the application of arrays in hashing

In the case of this question, the space consumption of using map is larger than that of array , because map needs to maintain red-black tree or hash table, and also needs to do hash function, which is time-consuming! If the amount of data is large, the difference can be reflected, so the array is simpler, more direct and effective

6.2 Code implementation (hash implementation)

// 时间复杂度: O(n)
// 空间复杂度: O(1)
#include <iostream>
#include <string>

using namespace std;

class Solution {
    
    
public:
    bool canConstruct(string ransomNote, string magazine) {
    
    
        int record[26] = {
    
    0};
        // 如果 ransomNote 的长度大于 magazine,则肯定无法由 magazine 组成
        if (ransomNote.size() > magazine.size()) {
    
    
            return false;
        }
        // 遍历 magazine 字符串中的每个字符,将其对应的 record 数组中的元素加一
        // 表示该字符在 magazine 中出现了一次
        for (int i = 0; i < magazine.length(); ++i) {
    
    
            record[magazine[i] - 'a']++;
        }
        // 遍历 ransomNote 字符串中的每个字符,将其对应的 record 数组中的元素减一
        // 表示尝试用该字符来构建 ransomNote
        for (int j = 0; j < ransomNote.length(); ++j) {
    
    
            record[ransomNote[j] - 'a']--;
            // 如果发现某个字符在 ransomNote 中的出现次数超过了在 magazine 中的出现次数
            // 则说明无法由 magazine 构成 ransomNote,返回 false
            if (record[ransomNote[j] - 'a'] < 0) {
    
    
                return false;
            }
        }
        return true;
    }
};

int main() {
    
    
    string ransomNote = "aa";
    string magazine = "aab";

    Solution solution;
    bool result = solution.canConstruct(ransomNote, magazine);
    if (result) {
    
    
        cout << "Ransom note can be constructed from magazine." << endl;
    } else {
    
    
        cout << "Ransom note cannot be constructed from magazine." << endl;
    }
    return 0;
}

7. Sum of three numbers

15. The sum of three numbers will
give you an integer array nums, and judge whether there is a triple [nums[i], nums[j], nums[k]] that satisfies i != j, i != k and j != k , while also satisfying nums[i] + nums[j] + nums[k] == 0. Please return all triples whose sum is 0 and are not repeated. Note: The answer cannot contain repeated triplets

  • Example 1
    Input: nums = [-1,0,1,2,-1,-4]
    Output: [[-1,-1,2],[-1,0,1]]
    Explanation:
    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 .
    The different triples are [-1,0,1] and [-1,-1,2].
    Note that the order of the output and the order of the triples is not important
  • Example 2
    Input: nums = [0,1,1]
    Output: []
    Explanation: The only possible triple sum is not 0
  • Example 3
    Input: nums = [0,0,0]
    Output: [[0,0,0]]
    Explanation: The only possible triple sum is 0
  • 提示
    3 <= nums.length <= 3000
    − 1 0 5 -10^5 105 <= nums[i] <= 1 0 5 10^5 105

7.1 Ideas

It is not appropriate to use the hash method for this topic, because there are many details that need to be paid attention to in the deduplication operation, and when the hash method uses two layers of for loops, the pruning operation that can be done is very limited, although the time complexity is O ( n 2 ) O(n^2)O ( n2 ), can also be passed on LeetCode, but the program execution time is longer

  • Here is another solution: the double-pointer method . The double-pointer method for this question is more efficient than the hash method.
    • 1. Take this nums array as an example, first sort the array , and then have a layer of for loop, i starts from the subscript 0, at the same time define a subscript left at the position of i+1, define the subscript right at the end of the array on the position
    • 2. Find abc in the array so that a + b + c = 0 ( here is equivalent to a = nums[i], b = nums[left], c = nums[right] )
    • 3. How to move left and right next?
      • If nums[i] + nums[left] + nums[right] > 0, it means that the sum of the three numbers is larger at this time, because the array is sorted, so the right subscript should move to the left, so that the three numbers and 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 moves to the right to make the sum of the three numbers larger until left and right meet

insert image description here

7.2 Code implementation

// 时间复杂度: O(n^2)
// 空间复杂度: O(1)
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
    
    
public:
    vector<vector<int>> threeSum(vector<int> &nums) {
    
    
        vector<vector<int>> result;
        sort(nums.begin(), nums.end()); // 首先将数组排序
        // 外层循环遍历整个数组,以每个元素作为第一个数(即假设该数在三元组中最小)
        // a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); ++i) {
    
    
            // 排序之后如果第一个元素已经 > 0,那么不可能凑成三元组
            if (nums[i] > 0) {
    
    
                return result;
            }
            // 对 a 去重
            if (i > 0 && nums[i] == nums[i - 1]) {
    
    
                continue;
            }
            int left = i + 1; // left 指向当前元素的下一个位置
            int right = nums.size() - 1; // right 指向数组末尾
            // 内层循环使用双指针技巧,在已排序的数组中查找剩余两个数
            while (right > left) {
    
    
                if (nums[i] + nums[left] + nums[right] > 0) {
    
    
                    right--; // right 下标向左移动才能让三数之和小一些
                } else if (nums[i] + nums[left] + nums[right] < 0) {
    
    
                    left++; // 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;
    }
};

int main(int argc, char *argv[]) {
    
    
    vector<int> nums = {
    
    -1, 0, 1, 2, -1, -4};
    Solution solution;
    vector<vector<int>> ans = solution.threeSum(nums);
    cout << "[";
    for (int i = 0; i < ans.size(); ++i) {
    
    
        cout << "[";
        for (int j = 0; j < ans[i].size(); ++j) {
    
    
            cout << ans[i][j];
            if (j != ans[i].size() - 1) {
    
    
                cout << ", ";
            }
        }
        cout << "]";
        if (i != ans.size() - 1) {
    
    
            cout << ", ";
        }
    }
    cout << "]" << endl;

    return 0;
}
// 输出
[[-1, -1, 2], [-1, 0, 1]]

7.3 a de-duplicated logical thinking

// 错误:把三元组中出现重复元素的情况直接 pass 掉了
    // 例如 {-1, -1, 2} 这组数据,当遍历到第一个 -1 的时候
    // 判断下一个也是 -1,那这组数据就 pass 掉了
    // 题意是不能有重复的三元组,但三元组内的元素是可以重复的
if (nums[i] == nums[i + 1]) {
    
    
    continue;
}
// 正确:这么写就是当前使用 nums[i],判断前一位是不是一样的元素
    // 再看 {-1, -1, 2} 这组数据,当遍历到第一个 -1 的时候,只要前一位没有 -1
    // 那么 {-1, -1, 2} 这组数据一样可以收录到结果集里
if (i > 0 && nums[i] == nums[i - 1]) {
    
    
    continue;
}

8. Sum of four numbers

18. The sum of four numbers
Give you an array nums consisting of n integers and a target value target. Please find and return quadruples [nums[a], nums[b], nums[c], nums[d]] that meet all the following conditions and are not repeated (if two quadruple elements correspond one-to-one , 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 answers in any order
  • Example 1
    Input: nums = [1,0,-1,0,-2,2], target = 0
    Output: [[-2,-1,1,2],[-2,0,0,2] ,[-1,0,0,1]]
  • Example 2
    Input: nums = [2,2,2,2,2], target = 8
    Output: [[2,2,2,2]]

8.1 Ideas

  • The sum of four numbers and the sum of three numbers are the same idea, both of which use the double pointer method. The basic solution is to add a layer of for loop on the basis of the sum of three numbers. But there are some details to pay attention to, such as
    • Don’t judge that nums[k] > target will return, the sum of three numbers can be returned by nums[i] > 0, because 0 is already a definite number , and the target of the sum of four numbers is an arbitrary value. For example: the array is [-4, -3, -2, -1], the target is -10, and cannot be skipped because -4 > -10. But you can still do pruning, the logic becomes nums[i] > target && (nums[i] >=0 || target >= 0)
    • The double pointer solution of the sum of four numbers is two layers of for loop nums[k] + nums[i] as the definite value, still there are left and right subscripts in the loop as double pointers, find out nums[k] + nums[i ] + nums[left] + nums[right] == ​​target

8.2 Code implementation

// 时间复杂度: O(n^3)
// 空间复杂度: O(1)
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
    
    
public:
    vector<vector<int>> fourSum(vector<int> &nums, int target) {
    
    
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 剪枝处理
        for (int k = 0; k < nums.size(); ++k) {
    
    
            if (nums[k] > target && nums[k] >= 0) {
    
    
                break; // 这里使用 break,统一通过最后的 return 返回
            }
            // 对 nums[k] 去重
            if (k > 0 && nums[k] == nums[k - 1]) {
    
    
                continue;
            }
            for (int i = k + 1; i < nums.size(); ++i) {
    
    
                // 二级剪枝处理
                if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
    
    
                    break;
                }
                // 对 nums[i] 去重
                if (i > k + 1 && nums[i] == nums[i - 1]) {
    
    
                    continue;
                }
                int left = i + 1;
                int right = nums.size() - 1;
                while (right > left) {
    
    
                    // nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
                    if ((long)nums[k] + nums[i] + nums[left] + nums[right] > target) {
    
    
                        right--;
                    } else if ((long)nums[k] + nums[i] + nums[left] + nums[right] < target) {
    
    
                        left++;
                    } else {
    
    
                        result.push_back(vector<int>{
    
    nums[k], nums[i], nums[left], nums[right]});
                        // 对 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;
    }
};

int main(int argc, char *argv[]) {
    
    
    vector<int> nums = {
    
    -1, 0, 1, 2, -1, -4};
    int target = -1;
    Solution solution;
    vector<vector<int>> ans = solution.fourSum(nums, target);
    cout << "[";
    for (int i = 0; i < ans.size(); ++i) {
    
    
        cout << "[";
        for (int j = 0; j < ans[i].size(); ++j) {
    
    
            cout << ans[i][j];
            if (j != ans[i].size() - 1) {
    
    
                cout << ", ";
            }
        }
        cout << "]";
        if (i != ans.size() - 1) {
    
    
            cout << ", ";
        }
    }
    cout << "]" << endl;

    return 0;
}
// 输出结果
[[-4, 0, 1, 2], [-1, -1, 0, 1]]

Guess you like

Origin blog.csdn.net/qq_42994487/article/details/130935245