写在前面

  • 主要是分类一下刷题遇到的一些题型。
  • 有很多思路的图都来源于力扣的题解,如侵权会及时删除。
  • 不过代码都是个人实现的,所以有一些值得记录的理解。

一、哈希表

1. 数组中重复的数字

  • 题目描述:https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/。
  • 思路
  • 由于n比较小,所以直接开一个和n等大的哈希表记录重复的击中即可。
  • 代码
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. 第一个只出现一次的字符

  • 题目:https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/

  • 思路
  • 用哈希表来记录每个字母出现的次数;
  • 注意:这里要求返回第一个出现次数为1的字母,而不是任意一个,因此还要用一个数组(或者队列)来记录key进入哈希表的顺序,即所谓的有序哈希;
  • 当然不记录也可以,直接再次访问字符串即可,但由于哈希表的大小(仅26)可能远小于字符串顺序,因此会更加耗时(虽说两种方式时间复杂度都是O(N));
  • 代码
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 ' ';}
};

二、二维矩阵

1. 二维数组中的查找

  • 题目描述:https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/

  • 思路
  • 利用其排序的特点,从矩阵的右上角,仿照二叉搜索树的方式查找

  • 代码
  • 下面的代码是以左下角为查找起点了,但效果是一样的;
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. 矩阵中的路径

  • 题目:https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/

  • 思路

  • 就是深度搜索和剪枝(一旦判断有成功就结束整个搜索)

  • 代码

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. 机器人的运动范围

  • 题目:https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/

  • 思路
  • 方法一:广度优先遍历,使用队列queue来辅助。
  • 方法二:深度优先遍历,使用递归搜索。
  • 但无论是广度优先遍历和深度优先遍历,都需要用一个visited矩阵记录走过的地方以便回溯;
  • 而且只用往右走和往下走即可,无需回头。
  • 方法三:直接遍历数组(相当于从地图的角度看),但要判断当前点是否可达。
  • 代码
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. 顺时针打印矩阵

  • 题目:https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/

  • 思路

  • 用一个大循环和四个次循环,同时维护四个边界变量,模拟顺时针走势;

  • 四个边界变量均为紧确界(闭区间),等号可以取到;

  • 所有的元素数量作为退出循环的标志,每个次循环做完之后都要判断一次;

  • 做完一行/一列之后,该边界值就要加1;

  • 返回空vector的写法为return vector<int>()(推荐)或者return {};(C++11);

  • 代码

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;  // 一行做完,上界+1if (n <= 0) {break;}for(i=ru, j=cr;i<=rb;++i) {re.push_back(matrix[i][j]);n--;}--cr;  // 一列做完,右界-1if (n <= 0) {break;}for(i=rb, j=cr;j>=cl;--j) {re.push_back(matrix[i][j]);n--;}--rb;  // 一行做完,下界-1if (n <= 0) {break;}for(i=rb, j=cl;i>=ru;--i) {re.push_back(matrix[i][j]);n--;}++cl;  // 一列做完,左界+1if (n <= 0) {break;}}}return re;}
};

三、字符串和回溯

1. 替换空格

  • 题目:https://leetcode.cn/problems/ti-huan-kong-ge-lcof/

  • 思路

  • 其实就是字符串的简单遍历处理。

  • 代码

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. 字符串的排列 [全排列]

  • 题目:https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/

  • 思路
  • 主要的难点是字符串中有重复的字符,因此需要从全排列中剔除掉这些重复的排列;
  • 思路是使用深度优先搜索+剪枝
  • 每一次深搜前都判断当前字符是否被处理过;
  • 通过交换字符可以达到遍历所有组合的效果(个人觉得这里设计得十分巧妙);
  • unordered_set可以判断重复元素,当然也可以用unordered_map
  • 代码
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. 数字序列中某一位的数字

  • 题目:https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/

  • 思路

  • 其实就是找规律,既要找到当前n表示的数字,也要记录它前面的数字和字符数量;

  • 注意计算的中间结果有可能超过int最大范围,一个简单粗暴的解决方法是将所有中间变量定义为long类型;

  • 提取数字的余数时用到了一个小技巧,就是将数字转成字符串再输出,这样就不用逆序求余输出了;

  • 代码

class Solution {/*0, 1-9, 1*9:110-99, 9+2*90:2100-999, 9+2*90+3*900:31000-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. 把数组排成最小的数

  • 题目:https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/

  • 思路

  • 就是将数组转成字符串按照两两拼接后的字典序进行排序,然后依次把排序后的序列拼接起来;

  • 所以核心是完成一个自定义规则的排序;

  • 自己实现的话当然是快排最好(立刻给我写个快排出来!叉腰 );

  • 如果是不自己实现而用sort函数的话也不错,时间上要比自己的快排要更快;

  • 注意sort函数是cmp函数的逻辑是:返回true则第一个参数排在前面;

  • 这应该算是一道经典的排序题;

  • 代码

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. 把字符串转换成整数

  • 题目:https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/

  • 思路
  • 非常繁琐但又不太繁琐的字符串处理;
  • 确实是比其他的算法来得“繁琐”,但在字符串处理里面又不算太繁琐(=0=~常规操作);

  • 难点是如何判断数字越界;

  • 这里非常巧妙的利用了数字在乘10之前的值和最大值作比较,而且同时根据res和x进行判断;

  • 负数是x>=8x>=8x>=8越界,正数是x>=7x>=7x>=7越界,这样可以避免出现正的2147483648

  • 代码

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;}}// 第一个字符不是数字,不能有效转换直接返回0if(str[i]<'0' || str[i]>'9') {            return 0;}// 转换数字int res = 0;int max_bound = 214748364; // 2^31-1 整除以 10while(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. 翻转单词顺序

  • 题目:https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/

  • 思路

  • 很麻烦的字符串处理,但符合字符串处理的一贯特点(也就是麻烦{{{(>_<)}}});

  • C++的做法利用类似双指针会比较好,别的方法也蛮复杂的,而且可以用的库函数不多;

  • 两个指针一个记录单词的起点-1,一个记录单词的终点,然后用substr函数截取字串;

  • 难点在于去除连续的空格,也要注意指针移动时字符串数组越界的问题;

  • 两个数组越界分别是写作了

    • while(i >= 0 && s[i] == ' ')
    • if(re.length() > 0 && re[re.length() - 1] == ' ')
    • 先判断是否越界再作进一步的判断;
  • 代码

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. 左旋转字符串

  • 题目:https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/

  • 思路
  • 不那么复杂的字符串处理;
  • 两种处理的思路:
    • 一种是通过切片来拼接字符串(这种比较API优雅);
    • 一种是通过循环求余来重组字符串(这种比较底层优雅);
    • 总之都可以很优雅 (不是) ;
  • 代码
  • 切片拼接:
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;}
};
  • 循环求余拼接:
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;}
};

四、链表

1. 从尾到头打印链表

  • 题目:https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/

  • 思路

  • 使用了额外的栈来辅助输出,涉及链表和栈的基本用法。

  • 代码

/*** 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. 删除链表的节点

  • 题目:https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/

  • 思路
  • 就是简单的链表删除节点操作。
  • 注意需要额外考虑头节点的删除。
  • 代码
/*** 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->nextpre->next = cur->next;}                //delete cur;cur = nullptr;pre = nullptr;break;}else{pre = cur;cur = cur->next;}}return head;}
};

3. 反转链表

  • 题目:https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/submissions/

  • 思路
  • 直接遍历一次整个链表,然后用一前一中一后三个指针直接修改节点之间的指向
  • 注意初始头节点->next最后要置空
  • 注意head==nullptr的情况
  • 代码
/*** 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. 合并两个排序的链表

  • 题目:https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/

  • 思路
  • 用一个新的头节点,然后逐个比较两个原有列表的头节点,逐个插入到新的链表中;
  • 如果不用伪头节点,则要单独处理头节点;
  • 下图是采用了伪头节点的,也就是prehead指向的节点,最终返回的是prehead->next
  • 别的链表的题目也可以尝试使用伪头节点来避免单独处理头节点;

  • 代码
/*** 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. 复杂链表的复制

  • 题目:https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/

  • 总的思路

  • 其实和普通的链表复制相比,这里的难点是要处理random指针指向的节点未新建情况;

  • 而且新节点random指向的是新节点值,且值要和原节点random指向的节点值相同;

  • 思路1

  • 递归处理random和next;

  • 如果指针指向的节点还没有新建,则继续递归先新建该复制节点,再返回来用指针指向;

  • 由于是随机新建复制节点,而不是顺序新建复制节点,所以还要用哈希表记录当前已经新建的复制节点;

  • 这样的时间复杂度是O(N),空间复杂度也是O(N),因为用了空间为N的哈希表;

  • 代码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];}}
};
  • 思路2
  • 分三次遍历原链表;
  • 第一次遍历只新建复制节点,同时将复制节点放在原节点之后;
  • 第二次遍历按照原链表的random指向,修改复制节点的random指向
  • 由于此时所有的复制节点都已经新建了,所以不会有random指向的节点仍没有新建的情况;
  • 第三次遍历分离两个链表
  • 这样的时间复杂度是O(N),空间复杂度降为O(1),避免了哈希表的使用;
  • 但时间复杂度上应该要高一点,这里是O(3N),上面思路1是O(N),虽然它们的数量级相同;

  • 代码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 == nullptrcur = cur->next->next;}// 两个链表分离Node* new_head = head->next;cur = head;Node* new_cur = new_head;while(cur != nullptr) {// cur在原序列中走到下一个curcur->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;}
};

五、二叉树

1. 重建二叉树

  • 题目:https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/submissions/

  • 思路

  • 先按照前序遍历确定根节点,再按照中序遍历划分子树

  • 通过哈希表快速定位根节点能够节省时间

  • 用递归的方式求解

  • 代码

/*** 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 valueint left_length = inorder_map[preorder[preorder_left]] - inorder_left;  // length of left sub-treeroot->left = myBuildTree(preorder_left+1, preorder_left+left_length, inorder_left, inorder_left+left_length-1, preorder, inorder);  // deal with the left treeint right_length = inorder_right - inorder_map[preorder[preorder_left]];  // length of right sub-treeroot->right = myBuildTree(preorder_left+1+left_length, preorder_right, inorder_left+left_length+1, inorder_right, preorder, inorder);  // deal with the right treereturn 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. 树的子结构

  • 题目:https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/

  • 思路

  • 代码
/*** 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. 二叉树的镜像

  • 题目:https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/

  • 思路

  • 就是用递归的思想;

  • 先递归处理左右子树,让左右子树镜像;

  • 然后再交换左右子树;

  • 代码

/*** 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. 对称的二叉树

  • 题目:https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/

  • 思路
  • 还是递归;
  • 转换为判断两棵子树是否为镜像;
  • 左树的左子树和右树的右子树镜像,左树的右子树和右树的左子树镜像;

  • 代码
/*** 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. 从上到下打印二叉树

  • 题目:https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/

  • 思路

  • 就是用广度优先遍历二叉树就可以了;

  • 注意考虑二叉树为空的情况;

  • 遍历时从队列取数据记得要同时完成s.front()s.pop()

  • 代码

/*** 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;}
};
变体1. 按层输出的从上到下打印二叉树
  • 题目:https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/

  • 思路1

  • 考虑用两个队列来实现,每个队列代表一层,轮流处理;

  • 但这种方式消耗会大一些;

  • 代码1

/*** 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;}
};
  • 思路2
  • 每次开始处理队列的时候,先记录队列的长度;
  • 因为这个时候队列中的所有元素都是同一层的;
  • 然后集中处理完这些元素,再进行下一次的队列处理;
  • 这样只需要用一个队列即可完成;
  • 代码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;}
};
变体2. 按层之字形输出的从上到下打印二叉树
  • 题目:https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/

  • 思路

  • 还是同变体1的思路,增加使用队列的长度来批量处理一层的元素;

  • 仍是用队列进行广度优先遍历;

  • 但存储元素的值使用双向队列deque代替vector,这样就可以做到不同层的反序输出;

  • 代码

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. 二叉搜索树的后序遍历序列

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

  • 思路

  • 注意不是普通的二叉树而是二叉搜索树

  • 二叉搜索树的特点是左子树的值均小于根节点,右子树的值均大于根节点;

  • 后序遍历的最后一个元素一定是根节点;

  • 因此核心思路就是先找到右子树,然后验证右子树的节点值是否都大于根节点;

  • 从左往右遍历到第一个大于根节点的值的节点(left+1),假定该节点左边均为左子树;

  • 从该节点开始验证右子树节点是否都大于根节点的值

  • 递归验证左子树和右子树;

  • 另外,要注意没有左子树或者没有右子树的情况;

  • 代码

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;// 找左子树根节点设为leftwhile(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重合,因为可能没有右子树,所以还是要用endreturn 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-1return 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. 二叉树中和为某一值的路径

  • 题目:https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/

  • 思路

  • 深度搜索遍历即可;

  • 用一个vector保存当前的路径;

  • 在叶子节点处,即cur->left==nullptr && cur->right==nullptr时判断路径是否符合要求;

  • 注意给定的叶子节点和路径和有可能是负数;

  • 代码

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) {// 当前是叶子节点且值为0re.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. 二叉搜索树与双向链表

  • 题目:https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/


  • 思路
  • 二叉搜索树能和双向链表相互转换的关键是:树有左右两个指针,双向链表也有前后两个指针;
  • 核心是用中序遍历,因为中序遍历二叉搜索树之后能够得到有序序列;
  • 可以简化成按照中序遍历打印节点的顺序处理节点,如下:
void dfs(Node* root) {if(root == nullptr) {return;}// 处理左节点dfs(root->left);// 处理当前节点printf("%d\n", root->val);// 处理右节点dfs(root->right);
}
  • 重点是只需处理当前的root节点,同时记录一个pre节点记录之前的一个节点;

  • 处理结束后需要额外修改头节点和尾节点的指针,形成循环链表;

  • 代码

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. 二叉搜索树的第k大节点

  • 题目:https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/

  • 思路

  • 因为二叉搜索树的中序遍历是有序的,所以仿照中序遍历即可;

  • 但注意这里找的是第k大的节点,而中序遍历是从小到大的,因此需要按照中序遍历的逆序,也就是“右->根->左”的顺序来遍历即可;

  • 要用两个全局变量保存当前计数和第k大节点的值;

  • 可以进行剪枝,因为一旦搜索到目标值,就不需要再往下遍历了;

  • 代码

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. 二叉树的深度

  • 题目:https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof/

  • 思路

  • 就做一遍遍历二叉树即可;

  • 一种思路是从顶往下记录深度,然后到null节点时用一个全局变量记录最大值,这种方式类似于先序遍历,根节点的处理就是将深度+1;

  • 另一种思路是从底往上返回左右子树的最大高度,最后从root返回最大的高度,这种实现起来代码更加简洁一点,但需要用后序遍历,因为root的高度由左右子树高度决定;

  • 代码

  • 下面的代码用了第二种思路实现:

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. 平衡二叉树

  • 题目:https://leetcode.cn/problems/ping-heng-er-cha-shu-lcof/

  • 思路
  • 10. 二叉树的深度很像;
  • 可以用从底往上的方法来遍历二叉树,也就是后序遍历;
  • 同时用一个全局变量记录结果,并可用于提前剪枝;
  • 剪枝是在每一个递归调用(除最后一个)后进行的,作用是避免进入之后的递归调用;
  • 当然提前剪枝后最终的返回值就不是树的高度了,虽然使用了int返回类型;
  • 代码
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. 二叉搜索树的最近公共祖先

  • 题目:https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/

  • 思路

  • 核心其实是利用了二叉搜索树来判断pq节点是在root的左子树还是右子树;

  • 实现的话是用了递归,当然也可以用while循环来迭代遍历,因为pq相对于root的位置是已知的,因此深搜的路径是确定的,无需回溯;

  • 代码

class Solution {public:/*root是最近公共祖先有三种情况:1. p和q分别在root的左右子树2. p在root的左子树或右子树,q在root3. 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;}
};
变体. 二叉树的最近公共祖先
  • 题目:https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/

  • 思路

  • 其实思路和上面的是很像的,只不过去掉了二叉搜索树这个前提,改为一般的二叉树;

  • 也就是说,现在无法通过单纯的值比较来确定pq节点是在左子树上还是右子树上;

  • 只能通过深度遍历搜索来确定,所以这样最坏情况下整棵树的所有节点都会被遍历到;

  • 值得注意的是,需要单独处理root是p或者q节点而另一个节点在它的左右子树中的情况,在这种情况下,root就是要找的最近父节点;

  • 代码

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;}
};

六、队列和栈

1. 用两个栈实现队列

  • 题目:https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/

  • 思路

  • 用两个栈,一个存输入数据,一个反序压栈,做输出。

  • 代码

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. 栈的压入、弹出序列

  • 题目:https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/

  • 思路

  • 直接用栈来模拟,验证能不能得到popped序列;

  • 分别用两个指针指向两序列当前正在验证的元素;

  • 栈每压入一个pushed的元素,就尝试弹出所有能和popped序列对应的元素;

  • 直到压入全部元素,此时验证popped序列对应元素是否都能被栈弹出(也就是指针是否到尽头);

  • 另外,两个序列长度不等时立刻就能判断它们一定不对应;

  • 使用s.top()前一定要先判断s.empty()

  • 代码

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. 包含min函数的栈 [单调栈]

  • 题目:https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/

  • 思路

  • 这里使用了单调栈的结构;

  • min_stack是单调非增序列,且为栈结构;

  • 这种结构可以维护一个滑动窗口最大值序列;

  • 但注意,单调栈只能维护一个最值,如果是要维护k个最值的话是不能用这种方法的,因为最大值入栈的时候必然是要把所有栈内元素弹出,因此无法维护k个最值,而是要用堆来实现,参考十一、1.最小的k个数

  • 代码

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. 滑动窗口的最大值 [单调栈]

  • 题目:https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/

  • 思路
  • 如果是暴力解的话,也就是用嵌套循环遍历,时间复杂度是O(k*N);
  • 其实这一题和上面的4. 滑动窗口的最大值很像,也是类似滑动窗口,也是要保存一系列最值(而非单个最值),因此也可以用单调栈解决,时间复杂度降为O(2N);

  • 这里虽然说是用了双端队列deque实现单调队列,但核心还是维护一个单调栈,栈内单调非增;
  • 那为什么不直接用栈来实现呢?主要是因为滑动窗口的左指针也移动,所以栈底(也就是队列首)的元素可能不在窗口内了,需要从栈底弹出元素,这是栈无法做到的;
  • 仅当栈底元素恰好等于滑动窗口左外第一个元素时,也就是刚好移出了滑动窗口时,才从栈底弹出元素,其他情况仍是维护一个单调栈;
  • 原因是入栈的顺序的按照遍历的顺序的,如果栈底的元素不是刚好移出窗口的那个元素,那么它和栈内的其他元素一定都是在窗口里面的元素(因为入栈时间晚);
  • 注意等于的元素也是要保留在栈内的,也就是说一定是要单调非增而不是单调减,这是为了应对当前元素是最大值(或者以后会成为最大值)的情况,因为所有的最大值或者潜在可能的最大值都应该保留在栈内;
  • 代码
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. 队列的最大值 [单调栈]

  • 题目:https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/

  • 思路

  • 还是单调栈的思路;

  • 其实是4. 滑动窗口的最大值的变体,相当于是将定长的滑动窗口改成变长的滑动窗口;

  • 在形式上和3. 包含min函数的栈是一样的,只是栈是先入后出,相当于滑动窗口的左指针不移动,队列是先入先出,左指针会一直移动;

  • 只要左指针移动,就需要使用双向队列来实现栈;

  • 注意是均摊的时间复杂度为O(1),因为就某次入队操作而言,max_queue可能不是1次操作就能完成的(也可能不需要操作),但所有元素入栈后,max_queue的总操作次数是O(N),所以均摊下来是O(1);

  • 代码

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();*/

剑指offer算法题01相关推荐

  1. java统计一个字符串中每个字符出现的次数_剑指offer算法题054:字符流中第一个不重复的字符...

    推荐阅读:宇宙条的工作总结:一年前还在面试找工作,一年后在面试找工作的学弟学妹们:第一次当面试官的经历分享小编在求职找找工作期间剑指offer上的算法题刷了很多遍,并且每道题小编当时都总结了一种最适合 ...

  2. 道指mt4代码_剑指offer算法题052:正则表达式匹配

    小编在求职找找工作期间剑指offer上的算法题刷了很多遍,并且每道题小编当时都总结了一种最适合面试时手撕算法的最优解法.考虑到剑指offer算法题在面试中的高频出现,小编每天和大家分享一道剑指offe ...

  3. 剑指offer算法题028:数组中出现次数超过一半的数字

    小编在求职找找工作期间剑指offer上的算法题刷了很多遍,并且每道题小编当时都总结了一种最适合面试时手撕算法的最优解法.考虑到剑指offer算法题在面试中的高频出现,小编每天和大家分享一道剑指offe ...

  4. 剑指offer算法题分析与整理(一)

    下面整理一下我在刷剑指offer时,自己做的和网上大神做的各种思路与答案,自己的代码是思路一,保证可以通过,网友的代码提供出处链接. 目录 1.数组中的逆序对 2.二维数组中的查找 3.顺时针打印矩阵 ...

  5. 剑指offer算法题分析与整理(二)

    下面整理一下我在刷剑指offer时,自己做的和网上大神做的各种思路与答案,自己的代码是思路一,保证可以通过,网友的代码提供出处链接. 目录 1.序列化二叉树 2.带记忆的DFS搜索 3.坐标数位和不大 ...

  6. 剑指offer算法题,输入一个链表,反转链表后,输出新链表的表头。

    链表的算法题一直做的浑浑噩噩的,为了避免忘记以及能够及时的温故知新,所以将本次的解答记录如下: 话不多说,先贴代码: /* public class ListNode {int val;ListNod ...

  7. 剑指offer算法题02

    写在前面 主要是题目太多,所以和前面的分开来记录. 有很多思路的图都来源于力扣的题解,如侵权会及时删除. 不过代码都是个人实现的,所以有一些值得记录的理解. 七.动态规划 1. 斐波那契数列 题目:h ...

  8. 剑指offer 刷题 01

    题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数 ...

  9. 最大连续子数组和 动态规划_剑指Offer算法题 33:连续子数组的最大和

    题目描述 HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决.但是,如果向量 ...

最新文章

  1. u8 采购到货单中的 业务类型 没有表字典,是系统预置 存入表也是文字: 普通采购 - 固定资产...
  2. QA发现的令人抓狂的BUG
  3. 核酸和CT同时用, 听谁的?——兼释一天新增一万多
  4. mapPartition方法与map方法的区别(转载)
  5. Angular应用的路由指令RouterLink
  6. C# WebService 上传图片
  7. 微信小程序 首页弹出用户协议
  8. JAVA WEB学习
  9. 初始化oracle环境失败,Oracle登录显示无法初始化
  10. 2016依然会给我惊喜,谢谢
  11. 基于java的学生宿舍管理系统(含源文件)
  12. storyboard 苹果启动图_iOS13 启动图适配
  13. 国产系统-Deepin安装图文(VIP典藏2022版)
  14. 环评师c语言题目,C语言考试——编程题_文库吧
  15. eclipse/UAP debug模式
  16. Web框架Django使用概览
  17. 百度回复针对目前相关性问题
  18. iOS学习—让View从下面弹出
  19. HDU-4510-日期
  20. solr6 mysql_solr6.6.0学习(2)创建核心和与Mysql数据库连接

热门文章

  1. 浅谈 找到最高海拔 问题
  2. ankiaddon编写 anki插件
  3. 23~49(构造函数+继承+类的本质+ES5中的新增方法)
  4. 外勤人员去哪儿了,在忙什么?
  5. 腾讯、华为、德鲁动力,为何众多科技厂商入局四足机器人?
  6. pta 乙1035 插入和归并 此题插入好写 归并必须自己模拟--
  7. 荷兰Flexible Optical B.V (OKO)公司
  8. 1024程序员节|赠送这15本书,获奖的任选 !
  9. c语言 某珠宝鉴定大赛,由主持人输入珠宝的真实价格,再由8位参赛选手输入各自估计的价格,估价与真实价格最接近的选手获胜。
  10. 多目标遗传算法电力系统系统分布式电源选址定容