[STL] Common function members in the six major components of STL and containers-(make up for an unfinished question)

Topic: Sum of four numbers

  • You are given an narray of integers numsand a target value target. Please find and return a non- duplicate quadruple that satisfies all the following conditions [nums[a], nums[b], nums[c], nums[d]](if two quadruple elements correspond one-to-one, the two quadruples are considered to be duplicates). Answers can be returned in any order :

    • 0 <= a, b, c, d < n

    • a, b, cand d are different from each other

    • nums[a] + nums[b] + nums[c] + nums[d] == target

answer
  • The simplest method is to use a quadruple loop to enumerate all quadruples, and then use a hash table to perform deduplication operations to get the final answer that does not contain duplicate quadruples. Assuming that the length of the array is nnn, the time complexity of the enumeration in this method is O ( n 4 ) O(n^4)O ( n4 ), the time complexity and space complexity of the deduplication operation are also very high, so a different approach is needed.

  • In order to avoid enumerating repeated quadruples, it is necessary to ensure that the elements enumerated in each cycle are not smaller than the elements enumerated in the previous cycle, and the same elements cannot be enumerated multiple times in the same cycle . In order to achieve the above requirements, the array can besort, and follow the following two points during the loop :

    • The subscript enumerated by each loop must be greater than the subscript enumerated by the previous loop;

    • In the same loop, if the current element is the same as the previous element, the current element is skipped.

  • Using the above method, you can avoid enumerating repeated quadruples, but since a quadruple loop is still used, the time complexity is still O ( n 4 ) O(n^4)O ( n4 ). Note thatthe array has been sorted, so a double pointer method can be used to eliminate a loop.

  • Use a double loop to enumerate the first two numbers respectively, and then use double pointers to enumerate the remaining two numbers after the numbers enumerated by the double loop. Assume that the first two numbers enumerated by the double loop are located at subscripts i and j respectively, where i<j. Initially, the left and right pointers point to subscript j+1 and subscript n−1 respectively. Calculate the sum of four numbers each time and perform the following operations:

    • If the sum is equal to target, add the four enumerated numbers to the answer, then move the left pointer right until a different number is encountered, and move the right pointer left until a different number is encountered;

    • If the sum is less than target, move the left pointer one position to the right;

    • If the sum is greater than target, move the right pointer one position to the left.

  • The time complexity of enumerating the remaining two numbers using double pointers is O(n), so the total time complexity is O ( n 3 ) O(n^3)O ( n3 ), lower thanO (n 4) O(n^4)O ( n4 ). During specific implementation, some pruning operations can also be performed:

    • After determining the first number, if nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target, it means that no matter what values ​​​​the remaining three numbers take at this time , the sum of the four numbers must be greater than target , so exit the first loop;

    • After determining the first number, if nums[i]+nums[n−3]+nums[n−2]+nums[n−1]<target, it means that no matter what values ​​​​the remaining three numbers take at this time , the sum of the four numbers must be less than the target, so the first loop directly enters the next round, enumerating nums[i+1];

    • After determining the first two numbers, if nums[i]+nums[j]+nums[j+1]+nums[j+2]>target, it means that no matter what the values ​​of the remaining two numbers are, four The sum of the numbers must be greater than the target, so exit the second loop;

    • After determining the first two numbers, if nums[i]+nums[j]+nums[n−2]+nums[n−1]<target, it means that no matter what the values ​​of the remaining two numbers are, four The sum of the numbers must be less than target, so the second loop directly enters the next round and enumerates nums[j+1].

  • class Solution {
          
          
    public:
        vector<vector<int>> fourSum(vector<int>& nums, int target) {
          
          
            vector<vector<int>> res;
            if(nums.size()<4)
                return res;
            sort(nums.begin(),nums.end());
            int len_nums = nums.size();
            for(int i=0;i<len_nums-3;i++){
          
          
                if(i>0&&nums[i]==nums[i-1])
                    continue;
                if((long) nums[i]+nums[i+1]+nums[i+2] +nums[i+3]>target)
                    break;
                if((long) nums[i] +nums[len_nums-1]+nums[len_nums-2]+nums[len_nums-3]<target)
                    continue;
                for(int j=i+1;j<len_nums-2;j++){
          
          
                    if(j>i+1&&nums[j]==nums[j-1])
                        continue;
                    if((long) nums[i] +nums[j]+nums[j+1]+nums[j+2]>target)
                        break;
                    if((long) nums[i]+nums[j]+nums[len_nums-1]+nums[len_nums-2]<target)
                        continue;
                    int left=j+1,right=len_nums-1;
                    while(left<right){
          
          
                        long sum = (long) nums[i]+nums[j]+nums[left]+nums[right];
                        if(sum==target){
          
          
                            res.push_back({
          
          nums[i],nums[j],nums[left],nums[right]});
                            while(left<right&&nums[right]==nums[right-1])
                                right--;
                            right--;
                        }else if(sum<target){
          
          
                            left++;
                        }else{
          
          
                            right--;
                        }
                    }
                }
            }
            return res;
        }
    };
    
  • Time complexity: O ( n 3 ) O(n^3)O ( n3 ), where n is the length of the array. The time complexity of sorting isO ( log ⁡ n ) O(\log n)O(logn ) , the time complexity of enumerating quadruples isO ( n 3 ) O(n^3)O ( n3 ), so the total time complexity isO (n 3 + n log ⁡ n) = O (n 3) O(n^3+n\log n)=O(n^3)O ( n3+nlogn)=O ( n3 ). Space complexity:O ( log ⁡ n ) O(\log n)O(logn ) , where n is the length of the array. The space complexity mainly depends on the additional space used by sorting. In addition, sorting modifies the input array nums, which may not be allowed in actual situations. Therefore, it can also be regarded as using an additional array to store a copy of the array nums and sorting it. The space complexity is O(n).

  • Generally speaking, hash tables are used to quickly determine whether an element appears in a set . For hash tables, you need to know the role of hash functions and hash collisions in hash tables. The hash function maps the incoming key to the index of the symbol table. Hash collision handles the situation when multiple keys are mapped to the same index. The common ways to handle collisions are the zipper method and the linear detection method.

STL basics

  • STL, the full name of standard template library in English, can be translated as standard template library or generic library in Chinese. It contains a large number of template classes and template functions. It is a collection of basic templates provided by C++ and is used to complete tasks such as input/output and mathematics. Calculation and other functions.

  • STL was originally developed by HP Labs and was established as an international standard in 1998, officially becoming an important part of the C++ program library. It is worth mentioning that STL is now completely built into compilers that support C++ , and no additional installation is required. This may be one of the reasons why STL is widely used. STL is located in the header file of each C++, that is, it is not provided in the form of binary code, but in the form of source code.

  • Fundamentally speaking, STL is a collection of containers, algorithms and other components. All containers and algorithms are implemented based on the summary of decades of research results on algorithms and data structures and the experience of many computer experts and scholars. Therefore, it can be said that STL has basically achieved a high degree of optimization of various storage methods and related algorithms .

  • Take the operation of defining an array in C++ as an example. If you define an array in C++, you can use the following method:

    • int a[n];
      
    • This method of defining an array requires the length of the array to be determined in advance, that is, n must be a constant . This means that if the length of the array cannot be determined in actual applications, the length of the array is generally set to the maximum possible value, but this is extremely This may result in a waste of storage space. Therefore, in addition to this, you can also useHeap spaceThe method of dynamically applying for memory, the length can be a variable :

    • int *p = new int[n];
      
  • This definition method can dynamically apply for memory according to the variable n, and there will be no problem of wasting storage space. However, if there is insufficient space during program execution, you need to increase the storage space. In this case, you need to perform the following operations:

    • Newly apply for a larger memory space, that is, executeint * temp = new int[m];

    • Copy all the data in the original , that is, executememecpy(temp, p, sizeof(int)*n);

    • Release the original heap space, that is, executedelete [] p; p = temp;

  • If you use the STL standard library to complete the same operation, it will be much simpler, because most of the operation details will not require programmers to care. The following is an example of using the vector template class vector to achieve the above functions:

    • vector <int> a; //定义 a 数组,当前数组长度为 0,但和普通数组不同的是,此数组 a 可以根据存储数据的数量自动变长。
      //向数组 a 中添加 10 个元素
      for (int i = 0; i < 10 ; i++)
          a.push_back(i)
      //还可以手动调整数组 a 的大小
      a.resize(100);
      a[90] = 100;
      //还可以直接删除数组 a 中所有的元素,此时 a 的长度变为 0
      a.clear();
      //重新调整 a 的大小为 20,并存储 20 个 -1 元素。
      a.resize(20, -1)
      
  • Comparing the above two ways of using arrays, it is not difficult to see that using STL can process data more conveniently and flexibly. Therefore, everyone only needs to study STL systematically, and then they can concentrate on realizing the functions of the program, without having to worry about how to implement certain details in code.

STL six major components

  • It is generally believed that STL is composed of 6 parts: containers, algorithms, iterators, function objects, adapters, and memory allocators, of which the last 4 parts serve the first 2 parts.

  • composition meaning
    container Some template classes that encapsulate data structures, such as vector vector containers, list list containers, etc.
    algorithm STL provides a large number (about 100) of data structure algorithms, all of which are designed as template functions. These algorithms are defined in the std namespace. Most of the algorithms are included in the header file, and a few of them are located in the header file. in the file.
    Iterator In C++ STL, reading and writing data in the container is done through iterators, which act as the glue between the container and the algorithm.
    function object If a class overloads the () operator as a member function, the class is called a function object class, and the objects of this class are function objects (also called functors).
    adapter You can adapt a class's interface (template parameters) to a user-specified form, allowing two classes that would otherwise not work together to work together. It is worth mentioning that containers, iterators and functions all have adapters.
    memory allocator Provide customized memory application and release functions for container class templates, because often only advanced users have the need to change the memory allocation strategy.
  • In the original release from HP Labs, the STL was organized into 48 header files; but in the C++ standard, they were reorganized into 13 header files

    • <iterator>;<functional>;<vector>;<deque>;<list>;<queue>;<stack>;<set>;<map>;<algorithm>;<numeric>;<memory>;<utility>
  • In the actual development process, rational organization of data access is as important as selecting algorithms for processing data. The way of accessing data often directly affects the complexity and time consumption of adding, deleting, modifying and querying them . In fact, when there are parts of the program that require high time consumption, the choice of data structure becomes particularly important, and sometimes even directly affects the success or failure of program execution.

  • To simply understand a container, it is a collection of template classes, but what is different from ordinary template classes is that the container encapsulates the method of organizing data (that is, the data structure). STL provides three types of standard containers, namely sequence containers, sorting containers and hash containers . The latter two types of containers are sometimes collectively referred to as associative containers.

    • Container type Function
      pecking order container It mainly includes vector vector container, list list container and deque double-ended queue container. It is called a sequence container because the position of the element in the container has nothing to do with the value of the element, that is, the container is not sorted . When inserting an element into a container, the element will be positioned where you specify it.
      sort container Including set collection containers, multiset multiple collection containers, map mapping containers and multimap multiple mapping containers. The elements in the sorting container are sorted from small to large by default . Even if an element is inserted, the element will be inserted into the appropriate position. So associative containers have very good performance when looking up.
      hash container C++ 11 adds 4 new associative containers, namely unordered_set hash set, unordered_multiset hash multiple set, unordered_map hash map and unordered_multimap hash multiple map. Unlike sorted containers, the elements in a hash container are unsorted, and the position of the elements is determined by the hash function.
  • The storage methods of the above three types of containers are completely different, so the efficiency of using different containers to complete the same operation is also very different. Therefore, in actual use, you must be good at choosing appropriate containers based on the functions you want to achieve.

  • Although the internal structures of different containers are different, they are essentially used to store large amounts of data. In other words, they are a string of storage units that can store multiple data. Therefore, operations such as data sorting, search, and summation that require data traversal should be similar.

  • Since they are similar, you can completely use generic technology to design them into universal algorithms that apply to all containers, thereby separating containers and algorithms . However, to achieve this goal, a device similar to an intermediary is required. In addition to the ability to traverse, read and write data in the container, it must also be able to hide the internal differences of the container from the outside, thereby transmitting data to the algorithm with a unified interface.

  • This is an inevitable result of the development of generic thinking, so iterators were born. To put it simply, an iterator is very similar to a C++ pointer. It can be of any type required. Through the iterator, you can point to an element in the container. If necessary, you can also read/write the element .

  • The STL standard library defines an iterator type for each standard container, which means that different containers have different iterators and their functional strengths are also different. The functionality of a container's iterator determines whether the container supports a certain algorithm in STL. Commonly used iterators are divided into five types according to their function : input iterator, output iterator, forward iterator, bidirectional iterator, and random access iterator .

  • forward iterator

    • Assuming that p is a forward iterator, p supports ++p, p++, *p operations, can also be copied or assigned, and can be compared with the == and != operators. Additionally, two forward iterators can be assigned to each other.
  • bidirectional iterator

    • Bidirectional iterators have all the functions of forward iterators. In addition, assuming p is a bidirectional iterator, you can also perform --p or p-- operations (that is, move backward one position at a time).
  • random access iterator

    • Random access iterators have the full functionality of bidirectional iterators. In addition, assuming that p is a random access iterator and i is an integer variable or constant, p also supports the following operations:

      • p+=i: Make p move i elements back.
      • p-=i: Make p move i elements forward.
      • p+i: Returns the iterator of the i-th element after p.
      • pi: Returns the iterator of the i-th element before p.
      • p[i]: Returns the reference to the i-th element after p.
    • In addition, two random access iterators p1 and p2 can also be compared using the <, >, <=, >= operators. In addition, the expression p2-p1 is also defined, and its return value represents the difference in the serial numbers of the element pointed by p2 and the element pointed by p1 (it can also be said that the number of elements between p2 and p1 is minus one).

  • Iterators from different containers

    • container The corresponding iterator type
      array Random access iterator
      vector Random access iterator
      therefore Random access iterator
      list bidirectional iterator
      set / multiset bidirectional iterator
      map / multimap bidirectional iterator
      forward_list forward iterator
      unordered_map / unordered_multimap forward iterator
      unordered_set / unordered_multiset forward iterator
      stack Iterators are not supported
      queue Iterators are not supported
    • The container adapter stack and queue do not have iterators. They contain some member functions that can be used to access elements.

  • Let's take the vector container as an example to actually experience the usage and function of iterators. Through the previous learning, vector supports random access iterators, so there are several ways to traverse the vector container. In the program below, each loop demonstrates one approach:

    • //遍历 vector 容器。
      #include <iostream>
      //需要引入 vector 头文件
      #include <vector>
      using namespace std;
      int main()
      {
              
              
          vector<int> v{
              
              1,2,3,4,5,6,7,8,9,10}; //v被初始化成有10个元素
          cout << "第一种遍历方法:" << endl;
          //size返回元素个数
          for (int i = 0; i < v.size(); ++i)
              cout << v[i] <<" "; //像普通数组一样使用vector容器
          //创建一个正向迭代器,当然,vector也支持其他 3 种定义迭代器的方式
          cout << endl << "第二种遍历方法:" << endl;
          vector<int>::iterator i;
          //用 != 比较两个迭代器
          for (i = v.begin(); i != v.end(); ++i)
              cout << *i << " ";
          cout << endl << "第三种遍历方法:" << endl;
          for (i = v.begin(); i < v.end(); ++i) //用 < 比较两个迭代器
              cout << *i << " ";
          cout << endl << "第四种遍历方法:" << endl;
          i = v.begin();
          while (i < v.end()) {
              
               //间隔一个输出
              cout << *i << " ";
              i += 2; // 随机访问迭代器支持 "+= 整数"  的操作
          }
      }
      
  • The so-called sequence container stores data of a specified type (such as int, double, etc.) in a linear arrangement (similar to the storage method of an ordinary array). It should be noted that this type of container will not automatically store the elements according to the Sort by value size. It should be noted that sequence containers are just a general term for a type of container and do not refer to a specific container. Sequence containers generally include the following types of containers :

    • array<T,N> (array container): It means that it can store N elements of type T. It is a container provided by C++ itself. Once such a container is created, its length is fixed, which means that elements cannot be added or deleted, only the value of an element can be changed ;

      • Insert image description here
    • vector<T> (vector container): used to store elements of type T. It is a sequence container with variable length. That is, when the storage space is insufficient, more memory will be automatically applied for . Using this container, adding or deleting elements at the tail is most efficient (time complexity is O(1) constant order), while inserting or deleting elements at other positions is less efficient (time complexity is O(n) linear order, where n is the number of elements in the container);

      • Insert image description here
    • deque<T> (double-ended queue container): very similar to vector. The difference is that using this container is not only efficient to insert and delete elements at the tail, but also efficient at inserting or deleting elements at the head . The time complexity is O(1) constant. order, but when inserting or deleting an element at a certain position in the container, the time complexity is O(n) linear order;

      • Insert image description here
    • list<T> (linked list container): is a variable-length sequence composed of T type elements, which starts withDoubly linked listElements are organized in a form, and elements can be added or deleted efficiently anywhere in this sequence (the time complexity is constant order O(1)), but the speed of accessing any element in the container is slower than the first three containers. This This is because list<T> must be accessed from the first element or the last element, and you need to move along the linked list until you reach the desired element.

      • Insert image description here
    • forward_list<T> (forward linked list container): very similar to the list container, except that it starts withSingle listThe elements are organized in a form, and the elements inside it can only be accessed starting from the first element. It is a type of container that is faster and more memory-saving than the linked list container.

      • Insert image description here

Common function members in containers

  • Function members of array, vector and deque containers

    • function member function function array<T,N> vector<T> deque<T>
      begin() Returns an iterator pointing to the first element in the container. yes yes yes
      end() Returns the position pointing to the last element of the containerlast oneiterator of positions yes yes yes
      rbegin() Returns an iterator pointing to the last element. yes yes yes
      rend() Returns the position pointing to the first elementPreviousIterator of positions. yes yes yes
      assign() Replace original content with new elements. yes yes
      operator=() Copies elements of a container of the same type, or replaces existing content with an initializer list. yes yes yes
      size() Returns the actual number of elements. yes yes yes
      max_size() Returns the maximum number of elements. This is usually a large value, typically 2 32 − 1 2^{32}-12321 yes yes yes
      capacity() 返回当前容量。
      empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
      resize() 改变实际元素的个数。
      shrink _to_fit() 将内存减少到等于当前元素实际所使用的大小。
      push_back() 在序列的尾部添加一个元素。
      pop_back() 移出序列尾部的元素。
      insert() 在指定的位置插入一个或多个元素。
      data() 返回指向容器中第一个元素的指针。
      swap() 交换两个容器的所有元素。
      clear() 移出一个元素或一段元素。
      erase() 移出所有的元素,容器大小变为 0。
      emplace() 在指定的位置直接生成一个元素。
  • list 和 forward_list 的函数成员

    • 函数成员 函数功能 list<T> forward_list<T>
      begin() 返回指向容器中第一个元素的迭代器。
      end() 返回指向容器最后一个元素所在位置后一个位置的迭代器。
      rbegin() 返回指向最后一个元素的迭代器。
      rend() 返回指向第一个元素所在位置前一个位置的迭代器。
      assign() 用新元素替换原有内容。
      operator=() 复制同类型容器的元素,或者用初始化列表替换现有内容。
      size() 返回实际元素个数。
      resize() 改变实际元素的个数。
      empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
      push_back() 在序列的尾部添加一个元素。
      push_front() 在序列的起始位置添加一个元素。
      emplace() 在指定位置直接生成一个元素。
      emplace_after() 在指定位置的后面直接生成一个元素。
      insert() 在指定的位置插入一个或多个元素。
      pop_back() 移除序列尾部的元素。
      pop_front() 移除序列头部的元素。
      reverse() 反转容器中某一段的元素。
      erase() 移除指定位置的一个元素或一段元素。
      erase_after() 移除指定位置后面的一个元素或一段元素。
      remove() 移除所有和参数匹配的元素。
      unique() 移除所有连续重复的元素。
      clear() 移除所有的元素,容器大小变为 0。
      swap() 交换两个容器的所有元素。
      sort() 对元素进行排序。
      merge() 合并两个有序容器。
      splice() 移动指定位置前面的所有元素到另一个同类型的 list 中。
      splice_after() Moves all elements after the specified position to another list of the same type. yes

Guess you like

Origin blog.csdn.net/weixin_43424450/article/details/132595905