Sword finger offer algorithm question 01

written in front

  • It is mainly to classify some types of questions encountered in brushing questions.
  • The pictures with many ideas are derived from Likou's solution, if there is any infringement, it will be deleted in time.
  • However, the code is implemented by individuals, so there are some understandings worth recording.

1. Hash table

1. Repeated numbers in array

class Solution {
    
    
public:
    int findRepeatNumber(vector<int>& nums) {
    
    
        int record[100000];
        for(int i=0;i<100000;i++)
        {
    
    
            record[i] = 0;
        }
        for(int i=0;i<nums.size();i++)
        {
    
    
            if(record[nums[i]])
            {
    
    
                return nums[i];
            }
            else
            {
    
    
                record[nums[i]] = 1;
            }
        }
        return -1;
    }
};

2. The first character that occurs only once

topic description

  • Ideas :
  • Use a hash table to record the number of occurrences of each letter;
  • Note: Here it is required to return the first letter whose occurrence is 1, not any one, so an array (or queue) is also used to record keythe order of entering the hash table, which is the so-called ordered hash;
  • Of course, it is also possible not to record, and you can directly access the string again, but since the size of the hash table (only 26) may be much smaller than the sequence of the string, it will be more time-consuming (although the time complexity of both methods is O(N ));
  • code :
class Solution {
    
    
public:
    char firstUniqChar(string s) {
    
    
        // 哈希表
        unordered_map<char, int> hash_map;
        // 按照入哈希表的顺序记录
        vector<char> order_arr;
        for(char c: s) {
    
    
            if(hash_map.count(c) != 0)
            {
    
    
                hash_map[c] += 1;
            }
            else
            {
    
    
                order_arr.push_back(c);
                hash_map[c] = 1;
            }
        }
        // 按照入哈希表的顺序查找
        for(int i=0;i<order_arr.size();++i) {
    
    
            if(hash_map[order_arr[i]] == 1) {
    
    
                return order_arr[i];
            }
        }
        return ' ';
    }
};

2. Two-dimensional matrix

1. Search in a two-dimensional array

topic description

  • Ideas :
  • Using its sorting characteristics, from the upper right corner of the matrix, search in the way of a binary search tree

topic description

  • code :
  • The following code uses the lower left corner as the starting point of the search, but the effect is the same;
class Solution {
    
    
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
    
    
        int i = matrix.size()-1;
        int j = 0;
        while(i>=0 && j<matrix[0].size())
        {
    
    
            if(matrix[i][j]==target)
            {
    
    
                return true;
            }
            else
            {
    
    
                if(matrix[i][j] < target)
                {
    
    
                    j++;
                }
                else
                {
    
    
                    i--;
                }
            }
        }
        return false;
    }
};

2. Paths in the matrix

class Solution {
    
    
private:
    bool trace(vector<vector<char>>& board, string &word, vector<vector<int>>& mark, int i, int j, int k)
    {
    
    
        if(k>=word.length())
        {
    
    
            // word被完整地遍历
            return true;
        }
        if(i<0 || j<0 || i>=board.size() || j>=board[0].size())
        {
    
    
            // 矩阵越界
            return false;
        }
        if(mark[i][j] == 1 || board[i][j] != word[k])
        {
    
    
            // 已被遍历或者不相等
            return false;
        }
        else
        {
    
    
            mark[i][j] = 1;
            // 分别从四个方向进行遍历
            if(trace(board, word, mark, i+1, j, k+1))
            {
    
    
                return true;
            }
            if(trace(board, word, mark, i, j+1, k+1))
            {
    
    
                return true;
            }
            if(trace(board, word, mark, i-1, j, k+1))
            {
    
    
                return true;
            }
            if(trace(board, word, mark, i, j-1, k+1))
            {
    
    
                return true;
            }
            // 遍历后恢复mark状态
            mark[i][j] = 0;
            return false;
        }
    }
public:
    bool exist(vector<vector<char>>& board, string word) {
    
    
        vector<vector<int>> mark(board.size(), vector<int>(board[0].size(), 0));
        for(int i=0;i<board.size();i++)
        {
    
    
            for(int j=0;j<board[0].size();j++)
            {
    
    
                // 逐个深度遍历
                if(trace(board, word, mark, i, j, 0))
                {
    
    
                    return true;
                }
            }
        }
        return false;
    }
};

3. Range of motion of the robot

topic description

  • Ideas :
  • Method 1: Breadth-first traversal, using queues to assist.
  • Method 2: Depth-first traversal, using recursive search.
  • But whether it is breadth-first traversal or depth-first traversal, it is necessary to use a visited matrix to record the places traveled for backtracking;
  • And just go right and down, no need to look back.
  • Method 3: Traverse the array directly (equivalent to looking at it from the perspective of the map), but it is necessary to judge whether the current point is reachable.
  • code :
class Solution {
    
    
private:
    bool isReachable(int x, int y, const int &k)
    {
    
    
        int sum = 0;
        while(x!=0)
        {
    
    
            sum += (x % 10);
            x = x / 10;
        }
        while(y!=0)
        {
    
    
            sum += (y % 10);
            y = y / 10;
        }
        if(sum<=k)
        {
    
    
            return true;
        }
        else
        {
    
    
            return false;
        }
    }

    /*广度优先遍历*/
    void trace_breadth(const int &m, const int &n, const int &k, int &count, vector<vector<int>> &visited)
    {
    
    
        //vector<vector<int>> visited(m, vector<int>(n, 0));  // 遍历记录矩阵

        queue<int> x_queue, y_queue;
        x_queue.push(0);
        y_queue.push(0);

        int x, y;
        while(!x_queue.empty())
        {
    
    
            x = x_queue.front();
            x_queue.pop();
            y = y_queue.front();
            y_queue.pop();

            if(x<m && y<n && !visited[x][y] && isReachable(x, y, k))
            {
    
    
                ++count;
                visited[x][y] = 1;
                x_queue.push(x+1);
                y_queue.push(y);
                x_queue.push(x);
                y_queue.push(y+1);
            }
        }        
    }

    /*深度优先搜索*/
    void trace_depth(int x, int y, const int &m, const int &n, const int &k, int &count, vector<vector<int>> &visited)
    {
    
    
        if(x<m && y<n && !visited[x][y] && isReachable(x, y, k))
        {
    
    
            ++count;
            visited[x][y] = 1;
            trace_depth(x+1, y, m, n, k, count, visited);
            trace_depth(x, y+1, m, n, k, count, visited);
        }
        return;
    }
public:
    int movingCount(int m, int n, int k) {
    
    
        int count = 0;
        vector<vector<int>> visited(m, vector<int>(n, 0));  // 遍历记录矩阵
        // 方法一
        //trace_breadth(m, n, k, count, visited);
        
        // 方法二
        //trace_depth(0, 0, m, n, k, count, visited);

        // 方法三:用数组的顺序遍历也可以,无需判断是否重复经过,但要判断是否可达
        for(int i=0;i<m;++i)
        {
    
    
            for(int j=0;j<n;++j)
            {
    
    
                bool mark = false;
                if(i-1>=0 && visited[i-1][j])
                {
    
    
                    mark = true;  // 从上面可达
                }
                if(j-1>=0 && visited[i][j-1])
                {
    
    
                    mark = true;  // 从左边可达
                }
                if(i==0 && j==0)
                {
    
    
                    mark = true;  // 原点
                }

                if(!isReachable(i, j, k))
                {
    
    
                    mark = false;        
                }

                if(mark)
                {
    
    
                    ++count;
                    visited[i][j] = 1;
                }
            }
        }

        return count;
    }
};

4. Print the matrix clockwise

topic description

  • Ideas :

  • Use one large cycle and four sub-cycles to maintain four boundary variables at the same time to simulate a clockwise trend;

  • The four boundary variables are all tight boundaries (closed intervals) , and the equal sign can be obtained;

  • Use all the number of elements as a sign to exit the loop, and judge once after each sub-loop is completed;

  • After completing a row/column, the boundary value will be increased by 1;

  • The way to return an empty vector is return vector<int>()(recommended) or return {};(C++11);

  • code :

class Solution {
    
    
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
    
    
        vector<int> re;

        // r->row, c->column, u->up, b->bottom, l:left, r->right;
        int ru = 0, cl = 0;
        int rb = matrix.size() - 1;
        if(rb >= 0) {
    
    
            int cr = matrix[0].size() - 1;

            // 总共是n个元素,作为退出循环的标志
            int n = matrix.size() * matrix[0].size();            
            int i, j;            
            while(true) {
    
    
                for(i=ru, j=cl;j<=cr;++j) {
    
    
                    re.push_back(matrix[i][j]);
                    n--;
                }
                ++ru;  // 一行做完,上界+1
                if (n <= 0) {
    
    
                    break;
                }
                for(i=ru, j=cr;i<=rb;++i) {
    
    
                    re.push_back(matrix[i][j]);
                    n--;
                }
                --cr;  // 一列做完,右界-1
                if (n <= 0) {
    
    
                    break;
                }
                for(i=rb, j=cr;j>=cl;--j) {
    
    
                    re.push_back(matrix[i][j]);
                    n--;
                }
                --rb;  // 一行做完,下界-1
                if (n <= 0) {
    
    
                    break;
                }
                for(i=rb, j=cl;i>=ru;--i) {
    
    
                    re.push_back(matrix[i][j]);
                    n--;
                }
                ++cl;  // 一列做完,左界+1
                if (n <= 0) {
    
    
                    break;
                }
            }
        }
        return re;
    }
};

3. Strings and Backtracking

1. Replace spaces

class Solution {
    
    
public:
    string replaceSpace(string s) {
    
    
        string re_s;
        re_s.resize(30000);
        int i, j=0;
        for(i=0;i<s.size();i++)
        {
    
    
            if(s[i] == ' ')
            {
    
    
                re_s[j++]='%';
                re_s[j++]='2';
                re_s[j++]='0'; 
            }
            else
            {
    
    
                re_s[j++]=s[i];
            }
        }
        return re_s;
    }
};

2. Arrangement of character strings [full arrangement]

topic description

  • Ideas :
  • The main difficulty is that there are repeated characters in the string, so these repeated permutations need to be removed from the full permutation;
  • The idea is to use depth-first search + pruning ;
  • Before each deep search, it is judged whether the current character has been processed;
  • The effect of traversing all combinations can be achieved by exchanging characters (I think the design here is very clever);
  • It unordered_setcan be used to judge repeated elements, of course it can also be used unordered_map;
  • code :
class Solution {
    
    
private:
    vector<string> re;
    void dfs(string &s, int x) {
    
    
        if(x == s.length()) {
    
    
            re.push_back(s);
            return;
        }
        unordered_set<char> set;
        for(int i=x;i<s.length();++i) {
    
    
            // 只有之前还没有处理过的情况下才进行深搜
            if(set.find(s[i]) == set.end()) {
    
    
                set.insert(s[i]);
                // 交换元素,因此字符串长度可以缩减
                swap(s[i], s[x]);
                dfs(s, x + 1);
                // 交换元素,复原字符串
                swap(s[i], s[x]);
            }
        }
    }

public:
    vector<string> permutation(string s) {
    
    
        dfs(s, 0);
        return re;
    }
};

3. A digit in a digit sequence

topic description

  • Ideas :

  • In fact, it is to find the law, not only to find the number represented by the current n, but also to record the number and number of characters in front of it;
    train of thought

  • Note that the intermediate results of the calculation may exceed the maximum range of int. A simple and crude solution is to define all intermediate variables as long type;

  • A little trick is used when extracting the remainder of a number, which is to convert the number into a string and then output it, so that there is no need to reverse the order for the remainder output;

  • code :

class Solution {
    
    
    /*
    0, 
    1-9, 1*9:1
    10-99, 9+2*90:2
    100-999, 9+2*90+3*900:3
    1000-9999, 9+2*90+3*900+4*9000:4
    重点是找到当前n所表示数字的digit_carry, digit_real_num, digit_char_num
    */
public:
    int findNthDigit(int n) {
    
    
        if(n < 10) {
    
    
            return n;
        }
        long digit_carry = 2;  // 进位数
        long digit_real_num = 9;  // 该进位数-1下的最大数字数量
        long digit_char_num = 9;  // 该进位数-1下的最大字符数量
        long digit_tmp = 9;
        while(digit_char_num + digit_carry*digit_tmp*10 < n) {
    
    
            digit_char_num += digit_carry*digit_tmp*10;
            digit_real_num += digit_tmp*10;
            digit_tmp = digit_tmp*10;
            ++digit_carry;
        }
        //printf("%d, %d, %d\n", digit_carry, digit_real_num, digit_char_num);
        int quotient = (n - digit_char_num) / digit_carry;
        int reminder = (n - digit_char_num) % digit_carry;
        int real_num = digit_real_num + quotient;
        //printf("%d, %d, %d\n", quotient, reminder, real_num);
        if(reminder > 0) {
    
    
            real_num += 1;
        }

        // to_string 函数:将数字常量转换为字符串,返回值为转换完毕的字符串
        string s = to_string(real_num);
        return s[(reminder+digit_carry-1)%digit_carry] - '0';
    }
};

4. Arrange the array into the smallest number

topic description

  • Ideas :

  • It is to convert the array into a string and sort it according to the lexicographical order after splicing in pairs, and then splice the sorted sequences in turn;

  • So the core is to complete the sorting of a custom rule;

  • Of course, if you realize it by yourself, quick row is the best ( write me a quick row immediately! Akimbo );

  • It’s not bad if you don’t implement it yourself but use sorta function, it’s faster than your own quick queue in terms of time;

  • Note that the logic sortthat a function is cmpa function is: trueif it returns, the first parameter is in front;

  • This should be regarded as a classic sorting problem;
    train of thought

  • code :

class Solution {
    
    
private:
    void quick_sort(vector<string> &str_arr, int low, int high) {
    
    
        if(low > high) {
    
    
            return;
        }
        string pivot = str_arr[low];
        int i = low;
        int j = high;
        while(i < j) {
    
    
            // 从high开始倒序遍历,把比pivot大的放左边
            while(i<j && pivot + str_arr[j] <= str_arr[j] + pivot) {
    
    
                --j;
            }
            if(i < j) {
    
    
                str_arr[i] = str_arr[j];
                ++i;
            }
            // 从low开始顺序遍历,把比pivot小的放右边
            while(i<j && str_arr[i] + pivot <= pivot + str_arr[i]) {
    
    
                ++i;
            }
            if(i < j) {
    
    
                str_arr[j] = str_arr[i];
                --j;
            }
        }
        str_arr[i] = pivot;
        quick_sort(str_arr, low, i-1);
        quick_sort(str_arr, i+1, high);
    }
public:
    string minNumber(vector<int>& nums) {
    
    
        vector<string> str_arr;
        // 转字符串数组
        for(int num: nums) {
    
    
            str_arr.push_back(to_string(num));
        }
        // 自己实现的快排
        // quick_sort(str_arr, 0, str_arr.size() - 1);
        // 用sort函数
        sort(str_arr.begin(), str_arr.end(), [](string &a, string &b) {
    
    
            if(a+b < b+a) {
    
    
                return true;
            }
            else {
    
    
                return false;
            }
        });
        // 拼接结果
        string re;
        for(string str: str_arr) {
    
    
            re.append(str);
        }
        return re;
    }
};

5. Convert a string to an integer

topic description

  • Ideas :
  • very tedious but not too tedious string handling;
  • It is indeed "complex" than other algorithms, but it is not too cumbersome in string processing (=0=~routine operation);

train of thought

  • The difficulty is how to judge the number out of bounds;

  • Here it is very clever to use the value of the number before multiplying by 10 to compare with the maximum value, and to judge according to res and x at the same time;

  • Negative numbers are x >= 8 x>=8x>=8 out of bounds, positive number isx >= 7 x>=7x>=7 out of bounds, so that positive ones can be avoided2147483648;
    train of thought

  • code :

class Solution {
    
    
public:
    int strToInt(string str) {
    
    
        int i = 0;
        // 处理开头的空格
        while(str[i] == ' ') {
    
    
            ++i;
        }
        // 处理'+'和'-'
        bool is_negative = false;
        if(str[i] == '-') {
    
    
            is_negative = true;
            ++i;
        }
        else {
    
    
            if(str[i] == '+') {
    
    
                // is_negative = false;
                ++i;
            }
        }
        // 第一个字符不是数字,不能有效转换直接返回0
        if(str[i]<'0' || str[i]>'9') {
    
                
            return 0;
        }
        // 转换数字
        int res = 0;
        int max_bound = 214748364; // 2^31-1 整除以 10
        while(str[i]>='0' && str[i]<='9') {
    
    
            int x = str[i] - '0';
            // 非常巧妙地在乘10之前就判断数字是否超过int最大值
            if(res > max_bound) {
    
    
                if(is_negative) {
    
     return -2147483648; }
                else {
    
     return 2147483647; }
            }
            if(res == max_bound) {
    
    
                // 这里的判断非常巧妙地使用了等于号,避免了出现正值的2147483648而溢出
                if(is_negative && x >= 8) {
    
     return -2147483648; }
                else {
    
     if(!is_negative && x >=7) {
    
     return 2147483647; } }
            }
            res = res * 10 + x;
            ++i;
        }
        // 转换正负值
        if(is_negative) {
    
    
            res = -res;
        }
        return res;
    }
};

6. Flip word order

topic description

  • Ideas :

  • Very troublesome string processing, but consistent with the consistent characteristics of string processing (that is, troublesome { { {(>_<)}}});

  • It would be better to use similar double pointers in C++, other methods are quite complicated, and there are not many library functions that can be used;

  • Two pointers, one to record words 起点-1, one to record words 终点, and then use substrthe function to intercept the string;

  • The difficulty lies in removing consecutive spaces , and also pay attention to the problem that the string array is out of bounds when the pointer is moved;

  • Two arrays out of bounds are written

    • while(i >= 0 && s[i] == ' ')
    • and if(re.length() > 0 && re[re.length() - 1] == ' ');
    • First judge whether it is out of bounds before making further judgments;
  • code :

class Solution {
    
    
public:
    string reverseWords(string s) {
    
    
        // 处理空字符串
        if(s.length() == 0) {
    
    
            return "";
        }
        string re;
        int i = s.length() - 1;
        int j;
        // 去除末尾的空格
        while(i >= 0 && s[i] == ' ') {
    
    
            --i;
        }
        j = i;
        while(i >= 0) {
    
                
            if(s[i] == ' ') {
    
    
                re.append(s.substr(i+1, j-i));
                re.append(" ");                
                // 去除单词之间的空格
                while(i >= 0 && s[i] == ' ') {
    
    
                    --i;
                }
                j = i;
            } 
            else {
    
    
                --i;
            }                
        }
        // 处理最后的单词
        if(i != j)
        {
    
    
            re.append(s.substr(i+1, j-i));
        }
        // 去除最后的空格
        if(re.length() > 0 && re[re.length() - 1] == ' ') {
    
    
            re = re.substr(0, re.length() - 1);
        }        
        return re;
    }
};

7. Rotate a string to the left

topic description

  • Ideas :
  • less complex string handling;
  • Two ways of dealing with it:
    • One is to splice strings by slicing (this kind of API is more elegant);
    • One is to reorganize the string by cyclic remainder (this is more elegant at the bottom);
    • All in all can be very elegant (not) ;
  • code :
  • Slice stitching:
class Solution {
    
    
public:
    string reverseLeftWords(string s, int n) {
    
    
        string re;
        re.append(s.substr(n, s.length() - n));
        re.append(s.substr(0, n));
        return re;
    }
};
  • Cyclic remainder concatenation:
class Solution {
    
    
public:
    string reverseLeftWords(string s, int n) {
    
    
        string re(s);
        for(int i=n;i<n+s.length();++i) {
    
    
            re[i-n] = s[i%s.length()];
        }
        return re;
    }
};

4. Linked list

1. Print the linked list from end to beginning

topic description

  • Ideas :

  • An additional stack is used to assist output, involving the basic usage of linked lists and stacks.

  • code :

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    
    
public:
    vector<int> reversePrint(ListNode* head) {
    
    
        ListNode* cur = head;        
        vector<int> arr;

        if(!cur)
        {
    
    
            return arr;
        }

        arr.push_back(cur->val);
        while(cur->next)
        {
    
    
            cur = cur->next;
            arr.push_back(cur->val);
        }
        vector<int> final_arr(arr);
        for(int i=0;i<arr.size();i++)
        {
    
    
            final_arr[i] = arr[arr.size() - 1 - i];
        }
        return final_arr;
    }    
};

2. Delete the node of the linked list

topic description

  • Ideas :
  • It is a simple linked list delete node operation.
  • Note that the deletion of the head node needs to be additionally considered.
  • code :
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    
    
public:
    ListNode* deleteNode(ListNode* head, int val) {
    
    
        ListNode *cur = head;
        ListNode *pre = head;
        while(cur)
        {
    
    
            if(cur->val==val)
            {
    
    
                if(cur==head)
                {
    
    
                    // 考虑头节点的移动,因为最终返回的是头节点
                    head = cur->next;
                }
                else
                {
    
    
                    // 注意是pre->next
                    pre->next = cur->next;
                }                
                //delete cur;
                cur = nullptr;
                pre = nullptr;
                break;
            }
            else
            {
    
    
                pre = cur;
                cur = cur->next;
            }
        }
        return head;
    }
};

3. Reverse linked list

topic description

  • Ideas :
  • Directly traverse the entire linked list once, and then directly modify the pointers between nodes with three pointers, one in front, one in one, and one in the back
  • Note that the initial head node -> next should be empty at the end
  • Attention head==nullptrto the situation
  • code :
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
        /*
        连续定义时每个指针变量都要加*
        */
        ListNode *first, *second, *temp;
        first = head;
        if(first)
        {
    
    
            second = head->next;
            while(second)
            {
    
    
                temp = second->next;
                second->next = first;
                first = second;
                second = temp;
            }
            head->next = NULL;
        }        
        return first;
    }
};

4. Merge two sorted lists

topic description

  • Ideas :
  • Use a new head node, then compare the head nodes of the two original lists one by one, and insert them into the new linked list one by one;
  • If the pseudo head node is not used, the head node must be processed separately;
  • The figure below uses the pseudo-head node, that is, preheadthe pointed node, and finally returns prehead->next;
  • For other linked list questions, you can also try to use pseudo-head nodes to avoid processing the head nodes separately;

train of thought

  • code :
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    
    
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    
    
        // 一方为空,则直接返回非空链表
        if(!l1)
        {
    
    
            return l2;
        }
        if(!l2)
        {
    
    
            return l1;
        }
        // 新建伪头节点
        ListNode* head = new ListNode(-1);

        // 处理两个非空链表
        // 非头节点用pre处理
        // 三步走:pre->next = node; node = node->next; pre = pre->next;
        ListNode* pre = head;
        while(l1 && l2)
        {
    
    
            if(l1->val <= l2->val)
            {
    
    
                pre->next = l1;
                l1 = l1->next;
            }
            else
            {
    
    
                pre->next = l2;
                l2 = l2->next;
            }
            pre = pre->next;
        }
        // 处理剩下的非空链表
        // 直接拼接就行
        if(l1)
        {
    
    
            pre->next = l1;
        }
        else
        {
    
    
            pre->next = l2;
        }
        // 伪头节点要舍弃
        return head->next;
    }
};

5. Replication of complex linked lists

topic description

  • General idea :

  • In fact, compared with ordinary linked list copying, the difficulty here is to deal with the fact that the node pointed to by the random pointer has not been newly created ;

  • And the new node random points to the new node value, and the value must be the same as the node value pointed to by the original node random;

  • Idea 1 :

  • Recursively process random and next;

  • If the node pointed to by the pointer has not been newly created, continue recursively to create the copy node first, and then return to point to it with the pointer;

  • Since the replication nodes are created randomly instead of sequentially, a hash table is also used to record the currently newly created replication nodes;

  • Such time complexity is O(N), and space complexity is also O(N) , because a hash table with space N is used;

  • Code 1 :

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
    
    
private:
    unordered_map<Node*, Node*> cachedMap;
public:
    Node* copyRandomList(Node* head) {
    
    
        if(head == nullptr) {
    
    
            return nullptr;
        }
        else {
    
    
            if(cachedMap.count(head) == 0) {
    
    
                cachedMap[head] = new Node(head->val);
                cachedMap[head]->next = copyRandomList(head->next);
                cachedMap[head]->random = copyRandomList(head->random);
            }
            return cachedMap[head];
        }
    }
};
  • Idea 2 :
  • Traverse the original linked list three times;
  • The first traversal only creates a new copy node , and at the same time puts the copy node behind the original node;
  • The second traversal modifies the random pointer of the copied node according to the random pointer of the original linked list ;
  • Since all the replication nodes have been newly created at this time, there will be no situation where the node pointed to by random is still not newly created;
  • The third traversal separates the two linked lists ;
  • This time complexity is O(N), and the space complexity is reduced to O(1) , avoiding the use of hash tables;
  • But the time complexity should be higher, here is O(3N), the above idea 1 is O(N), although their order of magnitude is the same;

Idea 2

  • Code 2 :
class Solution {
    
    
public:
    Node* copyRandomList(Node* head) {
    
    
        if(head == nullptr) {
    
    
            return nullptr;
        }
        Node* cur = head;
        // 原地拷贝
        while(cur != nullptr) {
    
    
            Node* tmp = new Node(cur->val);
            tmp->next = cur->next;
            cur->next = tmp;
            // cur只遍历原序列的节点
            cur = tmp->next;
        }
        // 改random指向
        cur = head;
        while(cur != nullptr) {
    
    
            if(cur->next->random == nullptr) {
    
    
                if(cur->random == nullptr) {
    
    
                    cur->next->random = nullptr;
                } 
                else {
    
    
                    cur->next->random = cur->random->next;
                }                
            }
            // cur只遍历原序列的节点,因为原序列也有可能random == nullptr
            cur = cur->next->next;
        }
        // 两个链表分离
        Node* new_head = head->next;
        cur = head;
        Node* new_cur = new_head;
        while(cur != nullptr) {
    
    
            // cur在原序列中走到下一个cur
            cur->next = new_cur->next;
            cur = cur->next;  
            if(cur != nullptr) {
    
    
                // new_cur在新序列中走到下一个new_cur
                // cur如果走到了尽头,那么new_cur就不需要再进一步了
                new_cur->next = cur->next;            
                new_cur = new_cur->next;
            }
        }
        return new_head;
    }
};

Five, binary tree

1. Rebuild the binary tree

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
    
    
private:
    unordered_map<int, int> inorder_map;
    TreeNode* myBuildTree(int preorder_left, int preorder_right, int inorder_left, int inorder_right, vector<int>& preorder, vector<int>& inorder){
    
    

        if(preorder_left>preorder_right)
        {
    
    
            return nullptr;  // edge control
        }

        TreeNode* root = new TreeNode(preorder[preorder_left]);  // record the root value

        int left_length = inorder_map[preorder[preorder_left]] - inorder_left;  // length of left sub-tree
        root->left = myBuildTree(preorder_left+1, preorder_left+left_length, inorder_left, inorder_left+left_length-1, preorder, inorder);  // deal with the left tree

        int right_length = inorder_right - inorder_map[preorder[preorder_left]];  // length of right sub-tree
        root->right = myBuildTree(preorder_left+1+left_length, preorder_right, inorder_left+left_length+1, inorder_right, preorder, inorder);  // deal with the right tree

        return root;
    }
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    
    
        for(int i=0;i<inorder.size();i++)
        {
    
    
            inorder_map[inorder[i]] = i;
        }
        return myBuildTree(0, preorder.size()-1, 0, inorder.size()-1, preorder, inorder);
    }
};

2. The substructure of the tree

topic description

  • Ideas :

train of thought

  • code :
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
    
    
private:
    // 判断A树是否包含同根B树
    bool recur(TreeNode* A, TreeNode* B) {
    
            
        if(B == nullptr) {
    
    
        	// B树为空,则匹配完成
            return true;
        }
        else {
    
    
        	// A树为空或者值不相等,则不匹配
            if(A == nullptr) {
    
    
                return false;
            }
            if(A->val != B->val) {
    
    
                return false;
            } 

            // 注意凡是A->***的情况都要提前判断A是否为空值
            return recur(A->left, B->left) && recur(A->right, B->right);
        }       
    }

public:
    // 判断A树是否包含B树子结构
    bool isSubStructure(TreeNode* A, TreeNode* B) {
    
            
        if(B == nullptr) {
    
    
            // 按照定义,B树为空则不可能是A的子结构
            return false;
        }
        else {
    
    
            if(A == nullptr) {
    
    
                return false;
            }
            // 注意凡是A->***的情况都要提前判断A是否为空值
            return recur(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
        }        
    }
};

3. Mirror image of binary tree

topic description

  • Ideas :

  • Just use recursive thinking;

  • First recursively process the left and right subtrees, and let the left and right subtrees mirror;

  • Then exchange the left and right subtrees;

  • code :

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
    
    
public:
    TreeNode* mirrorTree(TreeNode* root) {
    
    
        if(root == nullptr)
        {
    
    
            return nullptr;
        }
        // 递归处理左右子树
        TreeNode* temp_left = mirrorTree(root->left);
        TreeNode* temp_right = mirrorTree(root->right);
        // 交换左右子树
        root->left = temp_right;
        root->right = temp_left;
        return root;
    }
};

4. Symmetrical binary tree

topic description

  • Ideas :
  • or recursion;
  • Convert to judging whether two subtrees are mirror images;
  • The left subtree of the left tree mirrors the right subtree of the right tree, and the right subtree of the left tree mirrors the left subtree of the right tree;

train of thought

  • code :
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
    
    
private:
    bool check(TreeNode* left, TreeNode* right) {
    
    
        if(left == nullptr && right == nullptr)
        {
    
    
            // 左右两树都是空
            return true;
        }
        if(left == nullptr || right == nullptr) {
    
    
            // 只有一棵树为空
            return false;
        }
        if(left->val != right->val) {
    
    
            // 左右两树值不相等
            return false;
        }
        if(check(left->left, right->right) && check(left->right, right->left))
        {
    
    
            // 两两分别镜像
            return true;
        }
        else {
    
    
            return false;
        }
    }

public:
    bool isSymmetric(TreeNode* root) {
    
    
        if(root == nullptr) {
    
    
            // 使用->前首先要判断节点是否为空
            return true;
        }
        else {
    
    
            return check(root->left, root->right);
        }        
    }
};

5. Print a binary tree from top to bottom

topic description

  • Ideas :

  • Just use breadth-first to traverse the binary tree;

  • Pay attention to consider the case where the binary tree is empty;

  • When traversing, remember to fetch data from the queue at the same s.front()time s.pop();

  • code :

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
    
    
public:
    vector<int> levelOrder(TreeNode* root) {
    
    
        vector<int> re;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()) {
    
    
        	// 从队列中取数据
            TreeNode* tmp = q.front();
            if(tmp != nullptr) {
    
    
                re.push_back(tmp->val);
                q.push(tmp->left);
                q.push(tmp->right);                
            }
            // 从队列中删数据
            q.pop();
        }
        return re;
    }
};
Variant 1. Top-to-bottom printed binary tree output by layer
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
    
    
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
    
    
        vector<vector<int>> re;
        queue<TreeNode*> q1;
        queue<TreeNode*> q2;
        q1.push(root);
        while(!q1.empty() || !q2.empty()) {
    
    
            vector<int> cur;
            while(!q1.empty()) {
    
    
                TreeNode* tmp = q1.front();
                if(tmp != nullptr) {
    
    
                    cur.push_back(tmp->val);
                    q2.push(tmp->left);
                    q2.push(tmp->right);
                }
                q1.pop();
            }
            if(!cur.empty()) {
    
    
                re.push_back(cur);
            }          
            cur.clear();  
            while(!q2.empty())
            {
    
                    
                TreeNode* tmp = q2.front();
                if(tmp != nullptr) {
    
    
                    cur.push_back(tmp->val);
                    q1.push(tmp->left);
                    q1.push(tmp->right);
                }
                q2.pop();
            }
            if(!cur.empty()) {
    
    
                re.push_back(cur);
            }
        }
        return re;
    }
};
  • Idea 2 :
  • Every time you start processing the queue, first record the length of the queue;
  • Because all the elements in the queue are at the same level at this time;
  • Then concentrate on processing these elements, and then proceed to the next queue processing;
  • This can be done with only one queue;
  • Code 2 :
class Solution {
    
    
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
    
    
        vector<vector<int>> re;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()) {
    
    
            vector<int> cur;
            int q_size = q.size();
            // 记录当前队列的长度,这些元素都是同一层的
            for(int i=0;i<q_size;i++) {
    
    
                TreeNode* tmp = q.front();
                if(tmp != nullptr) {
    
    
                    cur.push_back(tmp->val);
                    q.push(tmp->left);
                    q.push(tmp->right);
                }
                q.pop();
            }
            if(!cur.empty()) {
    
    
                re.push_back(cur);
            }          
        }
        return re;
    }
};
Variant 2. Top-to-bottom printed binary tree output by layer zigzag

topic description

  • Ideas :

  • It is still the same idea as variant 1 , increasing the length of the queue to process the elements of a layer in batches;

  • Still use the queue for breadth-first traversal;

  • dequeHowever, the value of the storage element is replaced by a two-way queue vector, so that the reverse order output of different layers can be achieved;

  • code :

class Solution {
    
    
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
    
    
        vector<vector<int>> re;
        queue<TreeNode*> q;        
        bool is_left = true;
        q.push(root);
        while(!q.empty()) {
    
    
            int q_size = q.size();
            // 用deque代替vector保存序列
            deque<int> dq;
            for(int i=0;i<q_size;++i) {
    
    
                TreeNode* tmp = q.front();
                if(tmp != nullptr) {
    
    
                    if(is_left) {
    
    
                        // 从后压入,正序
                        dq.push_back(tmp->val);
                    }     
                    else {
    
    
                        // 从前压入,反序
                        dq.push_front(tmp->val);
                    }                               
                    // printf("%d\n", tmp->val);                       
                    q.push(tmp->left);
                    q.push(tmp->right);
                }
                q.pop();
            }
            if(!dq.empty()) {
    
    
                // emplace_back连续复制
                re.emplace_back(vector<int>{
    
    dq.begin(), dq.end()});
            } 
            // 翻转序列
            is_left = !is_left;    
        }
        return re;
    }
};

6. Post-order traversal sequence of binary search tree

  • Title : https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/
    topic description

  • Ideas :

  • Note that it is not an ordinary binary tree but a binary search tree ;

  • The characteristic of the binary search tree is that the value of the left subtree is smaller than the root node, and the value of the right subtree is larger than the root node;

  • The last element of post-order traversal must be the root node;
    Binary search tree post-order traversal

  • Therefore, the core idea is to find the right subtree first , and then verify whether the node values ​​of the right subtree are greater than the root node;

  • Traverse from left to right to the first node ( left+1) whose value is greater than the root node, assuming that the left side of the node is the left subtree;

  • From this node, verify whether the right subtree nodes are all greater than the value of the root node ;

  • Recursively verify the left subtree and the right subtree;

  • In addition, pay attention to the situation that there is no left subtree or no right subtree;

  • code :

class Solution {
    
    
private:
    // left:左子树根节点,right:右子树根节点,root:根节点
    bool backOrderTraverse(vector<int>& postorder, int begin, int end) {
    
    
        if(begin == end) {
    
    
            // 只有一个节点
            return true;
        }
        if(postorder[begin] < postorder[end]) {
    
    
            int left = begin;
            // 找左子树根节点设为left
            while(left+1<end && postorder[left + 1]<postorder[end]) {
    
    
                left++;
            }
            // 判断右子树的合法性
            for(int i=left+1;i<end;i++) {
    
    
                if(postorder[i]<postorder[end]) {
    
    
                    return false;
                }
            } 
            // left一定在end左边且不重合
            // 但left+1有可能和end重合,因为可能没有右子树,所以还是要用end
            return backOrderTraverse(postorder, begin, left) && backOrderTraverse(postorder, left+1, end);
        }
        else {
    
    
            // 没有左子树,直接判断右子树的合法性
            for(int i=begin;i<end;i++) {
    
    
                if(postorder[i]<postorder[end]) {
    
    
                    return false;
                }
            } 
            // 不可能没有右子树,所以可以用end-1
            return backOrderTraverse(postorder, begin, end-1);
        }
    }
public:
    bool verifyPostorder(vector<int>& postorder) {
    
    
        if(postorder.size() == 0) {
    
    
            return true;
        }
        else {
    
    
        	// 递归验证序列是否满足二叉搜索树定义
            return backOrderTraverse(postorder, 0, postorder.size()-1);
        }        
    }
};

7. The path summed to a certain value in the binary tree

topic description

  • Ideas :

  • In-depth search and traversal;

  • Save the current path with a vector;

  • At the leaf node, it is cur->left==nullptr && cur->right==nullptrimmediately judged whether the path meets the requirements;

  • Note that the sum of the given leaf nodes and paths may be negative;

  • code :

class Solution {
    
    
private:
    void traverse(TreeNode* cur, int rest, vector<int> &cur_path, vector<vector<int>> &re) {
    
    
        if(cur == nullptr) {
    
    
            // 遇到空节点就返回
            return;
        }
        cur_path.push_back(cur->val);
        if(rest - cur->val == 0 && cur->left == nullptr && cur->right == nullptr) {
    
    
            // 当前是叶子节点且值为0
            re.push_back(cur_path);
        }
        traverse(cur->left, rest-cur->val, cur_path, re);  // 遍历左边
        traverse(cur->right, rest-cur->val, cur_path, re);  // 遍历右边
        cur_path.pop_back();
    }
public:
    vector<vector<int>> pathSum(TreeNode* root, int target) {
    
    
        vector<vector<int>> re;
        vector<int> cur_path;
        traverse(root, target, cur_path, re);
        return re;
    }
};

8. Binary search tree and doubly linked list

Title Description 1
Title Description 2

  • Ideas :
  • The key to the mutual conversion between a binary search tree and a doubly linked list is: the tree has two pointers on the left and right, and the doubly linked list also has two pointers on the front and back;
  • The core is to use in-order traversal, because after in-order traversal of the binary search tree, an ordered sequence can be obtained;
  • It can be simplified to process the nodes in the order in which the inorder traversal prints the nodes, as follows:
void dfs(Node* root) {
    
    
    if(root == nullptr) {
    
    
        return;
    }
    // 处理左节点
    dfs(root->left);
    // 处理当前节点
    printf("%d\n", root->val);
    // 处理右节点
    dfs(root->right);
}
  • The point is that only the current root node needs to be processed, and a pre node is recorded to record a previous node at the same time;

  • After the processing is completed, it is necessary to modify the pointers of the head node and the tail node to form a circular linked list;

  • code :

class Solution {
    
    
private:
    Node *pre = nullptr, *head = nullptr;
    void dfs(Node* root) {
    
    
        if(root == nullptr) {
    
    
            return;
        }
        // 处理左节点
        dfs(root->left);
        // 处理当前节点
        if(pre == nullptr) {
    
    
            // 第一次访问到的节点是头节点
            pre = root;
            head = root;
        }
        else {
    
    
            pre->right = root;  // 前向指针
            root->left = pre;  // 后向指针
            pre = root;  // 移动pre指针
        }
        // 处理右节点
        dfs(root->right);
    }

public:
    Node* treeToDoublyList(Node* root) {
    
    
        if(root == nullptr) {
    
    
            return nullptr;
        }
        dfs(root);
        // 修改头尾指针为循环指向
        head->left = pre;
        pre->right = head;
        return head;
    }
};

9. The kth largest node of the binary search tree

topic description

  • Ideas :

  • Because the in-order traversal of the binary search tree is ordered, it can be followed by the in-order traversal;

  • But note that the k-th largest node is found here , and the in-order traversal is from small to large, so it needs to be traversed in the reverse order of the in-order traversal, that is, the order of "right->root->left";

  • To use two global variables to save the current count and the value of the kth largest node;

  • Pruning can be performed, because once the target value is searched, there is no need to traverse down;

  • code :

class Solution {
    
    
private:
    int re;
    int count;
    void traverse(TreeNode* root, int k) {
    
    
        if(root == nullptr) {
    
    
            return;
        }
        else {
    
    
            traverse(root->right, k);
            if(count == k) {
    
    
                // 提前剪枝
                return;
            }
            ++count;
            if(count == k) {
    
    
                re = root->val;
                return;
            }            
            traverse(root->left, k);
        }
    }
public:
    int kthLargest(TreeNode* root, int k) {
    
    
        count = 0;
        traverse(root, k);
        return re;
    }
};

10. Depth of binary tree

topic description

  • Ideas :

  • Just do a traversal of the binary tree;

  • One way of thinking is to record the depth from top to bottom, and then use a global variable to record the maximum value when reaching the null node. This method is similar to pre-order traversal. The processing of the root node is to increase the depth by 1;

  • Another way of thinking is to return the maximum height of the left and right subtrees from the bottom up, and finally return the maximum height from the root. The code is more concise in this implementation, but it needs to be traversed in postorder, because the height of the root is determined by the height of the left and right subtrees. Decide;

  • code :

  • The following code implements the second idea:

class Solution {
    
    
private:
    int traverse(TreeNode* root) {
    
    
        if(root == nullptr) {
    
    
            return 0;
        }
        return max(traverse(root->left), traverse(root->right)) + 1;
    }
public:
    int maxDepth(TreeNode* root) {
    
    
        return traverse(root);
    }
};

11. Balanced Binary Tree

topic description

  • Ideas :
  • Similar to 10. The depth of the binary tree ;
  • You can use the bottom-up method to traverse the binary tree, that is, post-order traversal;
  • At the same time, a global variable is used to record the results and can be used for early pruning;
  • Pruning is performed after each recursive call (except the last one), to avoid recursive calls after entering;
  • Of course, after pruning in advance, the final return value is not the height of the tree, although intthe return type is used;
  • code :
class Solution {
    
    
private:
    bool re;
    int traverse(TreeNode* root) {
    
    
        if(root == nullptr) {
    
    
            return 0;
        }
        int left = traverse(root->left);
        if(re == false) {
    
    
            // 提前剪枝
            return -1;
        }
        int right = traverse(root->right);
        if(abs(left - right) > 1) {
    
    
            re = false;
        }
        return max(left, right) + 1;
    }
public:
    bool isBalanced(TreeNode* root) {
    
    
        re = true;
        traverse(root);
        return re;
    }
};

12. Binary search tree nearest common ancestor

topic description

  • Ideas :

train of thought

  • The core is actually using the binary search tree to judge pwhether qthe node is in rootthe left subtree or the right subtree;

  • The realization is to use recursion , of course, you can also use while loop to iterate through , because the position relative to pand is known, so the path of deep search is determined, no need to backtrack;qroot

  • code :

class Solution {
    
    
public:
    /*
    root是最近公共祖先有三种情况:
    1. p和q分别在root的左右子树
    2. p在root的左子树或右子树,q在root
    3. q在root的左子树或右子树,p在root
    因此需要递归(也就是不是最近公共祖先)的情况只剩两种:
    1. p和q都在root的左子树
    2. p和q都在root的右子树
    二叉搜索树直接可以通过值来确定p和q是在左子树还是右子树
    */
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    
    
        if(p->val < root->val && q->val < root->val) {
    
    
            // 均在左子树上
            return lowestCommonAncestor(root->left, p, q);
        }
        if(p->val > root->val && q->val > root->val) {
    
    
            // 均在右子树上
            return lowestCommonAncestor(root->right, p, q);
        }
        // 剩下的是root为公共祖先的三种情况
        return root;
    }
};
Variant. The nearest common ancestor of the binary tree

topic description

  • Ideas :

  • In fact, the idea is very similar to the above, except that the premise of the binary search tree is removed and changed to a general binary tree;

  • pIn other words, it is now impossible to determine whether qthe sum node is on the left subtree or the right subtree through pure value comparison ;

  • Can only be determined by deep traversal search, so in the worst case all nodes of the entire tree will be traversed;

  • It is worth noting that the case where the root is an por qnode and another node is in its left and right subtrees needs to be dealt with separately. In this case, the root is the nearest parent node to be found;

  • code :

class Solution {
    
    
public:
    /*
    相当巧妙的递归,分成两个阶段:
    1. 还没有找到最近的父节点时 -> left和right若不为空,则代表该子树包含p或者q
        根据这个可以找到最近的父节点
    2. 已经找到最近的父节点时 -> left或者right若不为空,则代表该子树包含最近的父节点
        此时不可能出现左右子树不同时为空的情况,之后再逐层往回传递该父节点即可
    */
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    
    
        if(root == nullptr) {
    
    
            return nullptr;
        }
        if(root == p || root == q) {
    
    
            // 如果另一个节点在root的子树中
            // 则root节点其实就是所要找的最近父节点
            // 如果不在root子树中,也没有必要继续往下遍历了
            return root;
        }
        TreeNode *left = lowestCommonAncestor(root->left, p, q);
        TreeNode *right = lowestCommonAncestor(root->right, p, q);
        if(left == nullptr) {
    
    
            // p和q都不在左边
            // 也隐含了p和q既都不在左边也都不在右边的可能
            return right;
        }
        if(right == nullptr) {
    
    
            // p和q都不在右边
            return left;
        }
        // p和q分别位于左右子树中,则root是最近公共父节点
        return root;
    }
};

6. Queues and stacks

1. Implement a queue with two stacks

topic description

  • Ideas :

  • Use two stacks, one to store input data, and one to push the stack in reverse order for output.
    train of thought

  • code :

class CQueue {
    
    
    stack<int> input_stack, output_stack;
public:
    CQueue() {
    
    

    }

    /*
        双栈:一个用于读入数据,一个用于将数据反序并输出
        输入:直接压入栈A
        输出:栈B有就直接输出,没有就将A的所有元素过到B中
    */
    
    void appendTail(int value) {
    
    
        input_stack.push(value);
    }
    
    int deleteHead() {
    
    
        if(!output_stack.empty())
        {
    
    
            int re =  output_stack.top();
            output_stack.pop();
            return re;
        }
        else
        {
    
    
            if(input_stack.empty())
            {
    
    
                return -1;
            }
            else
            {
    
    
                while(!input_stack.empty())
                {
    
    
                    output_stack.push(input_stack.top());
                    input_stack.pop();
                }
                int re =  output_stack.top();
                output_stack.pop();
                return re;
            }
        }
    }
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */

2. Stack push and pop sequences

topic

  • Ideas :

  • Simulate directly with the stack to verify whether the popped sequence can be obtained;

  • Use two pointers to point to the elements currently being verified in the two sequences;

  • Every time a pushed element is pushed into the stack, it tries to pop all elements that can correspond to the popped sequence;

  • Until all the elements are pushed, at this time, verify whether the elements corresponding to the popped sequence can be popped from the stack (that is, whether the pointer has reached the end);

  • In addition, when the lengths of the two sequences are not equal, it can be immediately judged that they must not correspond;

  • s.top()Must judge before use s.empty();

  • code :

class Solution {
    
    
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
    
    
        stack<int> s;
        if(pushed.size() != popped.size()) {
    
    
            // 两个序列长度不相等
            return false;           
        }
        int index1 = 0;
        int index2 = 0;
        // 下面的while包含了两个序列为空的特殊情况
        while(index1 < pushed.size()) {
    
    
            s.push(pushed[index1]);
            ++index1;
            // 使用s.top()的时候一定要先判断s.empty()
            while(!s.empty() && s.top() == popped[index2]) {
    
    
                s.pop();
                ++index2;
            }
        }
        if(index2!=popped.size()) {
    
    
            return false;
        }
        else {
    
    
            return true;
        }
    }
};

3. The stack containing the min function [monotonic stack]

topic description

  • Ideas :

  • The monotonic stack structure is used here ;
    train of thought

  • min_stackIs a monotonous non-increasing sequence, and is a stack structure;

  • This structure can maintain a maximum sequence of sliding windows ;

  • But note that the monotonic stack can only maintain one maximum value . If you want to maintain k maximum values, you cannot use this method, because when the maximum value is pushed into the stack, all the elements in the stack must be popped out, so k cannot be maintained. The most value, but to use the heap to achieve, refer to Eleven, 1. The smallest k number ;

  • code :

class MinStack {
    
    
private:
    stack<int> s;
    stack<int> min_s;
public:
    /** initialize your data structure here. */
    MinStack() {
    
    

    }
    
    void push(int x) {
    
    
        s.push(x);
        if(min_s.empty() || x<=min_s.top()) {
    
    
        	// 维持一个单调非增的队列,相等的元素也需要入栈
            min_s.push(x);
        }
    }
    
    void pop() {
    
    
        if(min_s.top() == s.top()) {
    
    
        	// 栈顶比较相同才弹出
            min_s.pop();
        }
        s.pop();
    }
    
    int top() {
    
    
        return s.top();
    }
    
    int min() {
    
    
        // 栈顶的元素最小
        return min_s.top();
    }
};

4. The maximum value of the sliding window [monotonic stack]

topic description

  • Ideas :
  • If it is a violent solution, that is, to use nested loop traversal, the time complexity is O(k*N);
  • In fact, this question is very similar to 4. The maximum value of the sliding window above . It is also similar to the sliding window. It also needs to save a series of maximum values ​​(rather than a single maximum value), so it can also be solved with a monotone stack , and the time complexity is reduced to O(2N);

train of thought

  • Although it is said here that a double-ended queue is used dequeto implement a monotonic queue, the core is to maintain a monotonic stack, and the stack is monotonically non-increasing;
  • So why not implement it directly with the stack? The main reason is that the left pointer of the sliding window also moves , so the element at the bottom of the stack (that is, the head of the queue) may not be in the window, and the element needs to be popped from the bottom of the stack, which cannot be done by the stack;
  • Only when the element at the bottom of the stack is exactly equal to the first element on the left side of the sliding window, that is, when the sliding window is just moved out, the element is popped from the bottom of the stack. In other cases, a monotonic stack is still maintained;
  • The reason is that the order of stacking is in accordance with the order of traversal. If the element at the bottom of the stack is not the element that just moved out of the window, then it and other elements in the stack must be elements in the window (because the stacking time is late);
  • Note that the elements that are equal to are also kept in the stack, that is to say, it must be monotonically non-increasing instead of monotonically decreasing. This is to deal with the situation that the current element is the maximum value (or will become the maximum value in the future), because all the maximum value or potentially the largest possible value should be kept on the stack;
  • code :
class Solution {
    
    
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    
    
        deque<int> queue;
        vector<int> re;
        int i;
        // 滑动窗口还没有形成
        for(i=0;i<k;++i) {
    
                
            while(!queue.empty() && queue.back() < nums[i]) {
    
    
                queue.pop_back();
            }  
            queue.push_back(nums[i]);          
        }
        re.push_back(queue.front());
        // 滑动窗口已形成
        for(i=k;i<nums.size();++i) {
    
                
            // 移除滑动窗口最左侧的最大值
            if(queue.front() == nums[i-k]) {
    
    
                queue.pop_front();
            }
            // 确保queue.front()是最大值且queue中的值均大于等于nums[i]
            // 等于一定要保留,否则会将多个相等的最大值都移除queue,即使它们在滑动窗口中
            // 也就是说queue只能是一个单调非增队列
            // 因为是单调非增,所以只能从后面删,此时的queue充当stack,即维护一个单调栈
            while(!queue.empty() && queue.back() < nums[i]) {
    
    
                queue.pop_back();
            }            
            queue.push_back(nums[i]);
            re.push_back(queue.front());
        }
        return re;
    }
};

5. The maximum value of the queue [monotonic stack]

topic description

  • Ideas :

  • Still the idea of ​​monotonic stack ;

  • In fact, it is a variant of 4. The maximum value of the sliding window , which is equivalent to changing a fixed-length sliding window into a variable-length sliding window;

  • In form, it is the same as 3. The stack containing the min function, except that the stack is first in first out, which is equivalent to the left pointer of the sliding window not moving, the queue is first in first out, and the left pointer will always move ;

  • As long as the left pointer moves, you need to use a two-way queue to implement the stack;

  • Note that the amortized time complexity is O(1), because as far as a certain enqueue operation is concerned, it max_queuemay not be completed in one operation (or no operation may be required), but after all elements are pushed onto the stack, max_queuethe total operation The number of times is O(N), so it is O(1) evenly;

  • code :

class MaxQueue {
    
    
private:
    deque<int> max_queue;  // 用双端队列实现单调栈
    queue<int> queue;
public:
    MaxQueue() {
    
    

    }
    
    int max_value() {
    
    
        if(queue.empty()) {
    
    
            return -1;
        }
        return max_queue.front();
    }
    
    void push_back(int value) {
    
    
        queue.push(value);
        while(!max_queue.empty() && max_queue.back() < value) {
    
    
            // 维持max_queue内单调非增的性质
            max_queue.pop_back();
        }
        max_queue.push_back(value);
    }
    
    int pop_front() {
    
     
        if(queue.empty()) {
    
    
            return -1;
        }
        int re = queue.front();       
        if(max_queue.front() == queue.front()) {
    
    
            // 如果queue弹出的值恰好等于最大值,则单调栈也弹出栈底元素
            // 也就是移除滑动窗口最左侧的最大值
            max_queue.pop_front();
        }
        queue.pop();
        return re;
    }
};

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue* obj = new MaxQueue();
 * int param_1 = obj->max_value();
 * obj->push_back(value);
 * int param_3 = obj->pop_front();
 */

Guess you like

Origin blog.csdn.net/weixin_43992162/article/details/126031353