目录
- 栈与队列
- 栈用于匹配的问题
- 队列用于堆
- 二叉树系列
- 深度遍历,递归与迭代
- 层序遍历
- 二叉树属性
- 二叉树修改与构造
- 二叉搜索树
- 公共祖先
- 二叉搜索树的修改与构造
栈与队列
栈用于匹配的问题
20. 有效的括号
https://leetcode-cn.com/problems/valid-parentheses/
不匹配的三种情况;
1、字符串左方向的括号多余了
2、字符串右方向的括号多余了
3、字符串括号没有多余,但是类型不匹配。
使用栈的时候三者对应到的栈的情况;
1、已经遍历完字符串,但是栈不为空
2、遍历字符串匹配的过程中,栈已经为空了
3、再遍历字符串匹配的过程中,发现栈中没有要匹配的字符。
class Solution {
public:bool isValid(string s) {stack<int> st;for(int i = 0; i < s.size(); i++){if(s[i] == '(') st.push(')');else if(s[i] == '[') st.push(']');else if(s[i] == '{') st.push('}');//接下来就是判断,这个是1、3情况else if(st.empty() || st.top() != s[i]){return false;}//如果匹配,那么出栈else{st.pop();}}//第2种情况,遍历完字符串,栈不为空return st.empty();}
};
1047. 删除字符串中的所有相邻重复项,与上一题思路一致,注意reverse函数的使用。
https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/
class Solution {
public:string removeDuplicates(string s) {stack<char> st;for(int i = 0; i < s.size(); i++){if(!st.empty() && st.top() == s[i]){st.pop();}elsest.push(s[i]);}string result ="";//将stack中数据输出while(!st.empty()){result += st.top();st.pop();} //然后倒序reverse(result.begin(),result.end());return result;}
};
150. 逆波兰表达式求值,做计算器程序的肯定知道这个,不过一般来说我们编写的是将中缀表达式转换为逆波兰表达式。
逆波兰表达式适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
与前几题一样思路,先将stirng转换成int入栈,tokens[i]是运算符,使用该运算符计算栈顶前两个元素。
需要注意的编程细节:
1、stack.pop()并不返回栈顶元素。所以要在pop之前,取top元素。
2、 stoi(s1)可以将string类型数据转化为int类型,比较方便。
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for(int i = 0; i < tokens.size(); i++){if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/"){int num2 = st.top();st.pop();int num1 = st.top();st.pop();if(tokens[i] == "+"){st.push(num1 + num2);}else if(tokens[i] == "-"){st.push(num1 - num2);}else if(tokens[i] == "*"){st.push(num1 * num2);}else{st.push(num1 / num2);}}else{st.push(stoi(tokens[i]));}}return st.top();}
};
队列用于堆
347. 前 K 个高频元素,思路简单,对容器的使用比较复杂。
1、统计元素出现频率,使用map
2、对频率进行排序,使用优先级队列(从队头取元素,从队尾添加元素,队列内部自动按照元素的权值排序)
3、找出前K个高频元素,使用小顶堆,小顶堆每次将最小的元素弹出,最后小顶堆中积累的才是K个最大元素。
构建过程:
1、根据频率,构建map
2、构建小顶堆,将所有频率送入堆中,如果堆的大小大于K了,将元素从堆顶弹出。
这里需要注意priority_queue的设置方式:
priority_queue<Type, Container(Type), Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆.
这里我们设置成这样:
送入的是pair对(这里指map),比较方式需要自定义,这里比较的是pair对第二个元素的值,从小到大排序。(注意优先队列以vector为容器,队首指向vector后面,队尾指向vector前面)
priority_queue< pair<int,int>,vector< pair<int,int> >,mycomparison > pri_que;
class Solution {
public://定义优先队列的排序方式,根据pair的第二个元素的大小,大的排后面。class mycomparison {public:bool operator()(const pair<int,int>& lhs,const pair<int,int>& rhs) {return lhs.second > rhs.second;}};vector<int> topKFrequent(vector<int>& nums, int k) {//统计元素出现频率unordered_map<int,int> map; //map<nums[i],对应频次>for(int i = 0; i < nums.size(); i++)map[nums[i]]++;//对频率排序//定义一个小顶堆,大小为kpriority_queue< pair<int,int>,vector< pair<int,int> >,mycomparison > pri_que;//用固定大小k的小顶堆扫描所有频率的数值for(unordered_map<int,int>::iterator it = map.begin(); it != map.end(); it++){pri_que.push(*it);if(pri_que.size() > k) pri_que.pop();}//找出前k个高频元素,因为小顶堆先弹出的是最小的,所以倒序输出到数组中。vector<int> result(k);for(int i = k - 1; i >= 0; i--){result[i] = pri_que.top().first;pri_que.pop();}return result;}
};
二叉树系列
二叉树链式存储方式:
struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x): val(x), left(nullptr), right(nullptr) {}
};
深度遍历,递归与迭代
三种遍历方式:
144. 二叉树的前序遍历
https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
145. 二叉树的后序遍历
https://leetcode-cn.com/problems/binary-tree-postorder-traversal/
94. 二叉树的中序遍历
https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
递归法模板:前序遍历为例
class Solution {
public:void traversal(TreeNode* cur,vector<int>& vec){if(cur == nullptr) return;vec.push_back(cur->val);traversal(cur->left,vec);traversal(cur->right,vec);}vector<int> preorderTraversal(TreeNode* root) {vector<int> result;traversal(root,result);return result;}
};
迭代法模板:中序遍历为例
注意:栈的特性入栈和出栈相反,所以如果想输出顺序为“左中右”,入栈顺序必须为“右中左”
class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {vector<int> result;stack<TreeNode*> st;if(root != nullptr) st.push(root);while(!st.empty()){TreeNode* node = st.top();if(node != nullptr){st.pop();if(node->right != nullptr) st.push(node->right);st.push(node);st.push(nullptr);if(node->left != nullptr) st.push(node->left);}else{st.pop();result.push_back(st.top()->val);st.pop();}}return result;}
};
层序遍历
层序遍历模板如下:
class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {queue<TreeNode*> que;if(root != nullptr) que.push(root);vector<vector<int>> result;while(!que.empty()) {int size = que.size();vector<int> vec;for(int i = 0; i < size; i++){TreeNode* node = que.front();que.pop();vec.push_back(node->val);if(node->left) que.push(node->left);if(node->right) que.push(node->right);}result.push_back(vec);}return result;}
};
102. 二叉树的层序遍历
https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
107. 二叉树的层序遍历 II
https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/
199. 二叉树的右视图
https://leetcode-cn.com/problems/binary-tree-right-side-view/
637. 二叉树的层平均值
https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/
429. N 叉树的层序遍历
https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/
515. 在每个树行中找最大值,注意max初始值取最小值INT_MIN
https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/
116. 填充每个节点的下一个右侧节点指针,这一题要注意同层节点之间的链接,在单层遍历的时候记录本层的头部节点,然后在遍历的时候让前一个节点指向本节点,本层最后一个节点next指向nullptr。
117. 填充每个节点的下一个右侧节点指针 II,这两题代码一样。
https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/
https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/
class Solution {
public:Node* connect(Node* root) {queue<Node*> que;if(root != nullptr) que.push(root);while(!que.empty()){int size = que.size();Node* pre;Node* now;for(int i = 0; i < size; i++){//每层第一个元素if(i == 0){pre = que.front();que.pop();now = pre;}else //非第一个元素{now = que.front();que.pop();pre->next = now;pre = now;}if(now->left) que.push(now->left);if(now->right) que.push(now->right);}//每一层最后一个元素pre->next = nullptr;}return root;}
};
104. 二叉树的最大深度,层序遍历,在while循环中++;
https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
111. 二叉树的最小深度,一样的思路,不过在每层节点遍历时需要检查是否无左右孩子,如果没有,则立刻返回当前层数。
if(!node->left && !node->right) return result;
二叉树属性
101. 对称二叉树
https://leetcode-cn.com/problems/symmetric-tree/
递归法:外层是对称的,内层也是对称的。
确定递归函数参数、返回值:
bool compare(TreeNode* left,TreeNode* right)
确定终止条件:
1、左右都为空,返回true
2、左右只有一个为空,返回false
3、左右结点均不为空,比较结点数值,不相同返回false
4、左右结点均不为空,数值相同时,还要继续向下判断
确定单层逻辑;
1、比较二叉树外侧是否对称:传入左结点的左孩子,右结点的右孩子。
2、比较内侧是否对称,传入左结点的右孩子,右结点的左孩子。
3、如果左右都对称就返回true,否则返回false
class Solution {
public:bool compare(TreeNode* left,TreeNode* right){if(!left && !right) return true;else if((!left && right) || (left && !right)) return false;else if(right->val != left->val) return false;//下面就是right->val == left->val的情况了,还得继续探讨bool outside = compare(right->right,left->left);bool inside = compare(right->left,left->right);return (outside && inside);}bool isSymmetric(TreeNode* root) {if(root == nullptr) return true;return compare(root->left,root->right);}
};
关于二叉树的最小深度与最大深度,在层序遍历中已经涉及到了
…在此省略
222. 完全二叉树的节点个数,层序遍历+节点计数
https://leetcode-cn.com/problems/count-complete-tree-nodes/
110. 平衡二叉树
https://leetcode-cn.com/problems/balanced-binary-tree/
递归三部曲:
1、确定递归函数的参数和返回值
传入当前节点,返回值为当前节点的高度。返回-1表示不是平衡二叉树
2、终止条件
遇到空节点为终止,返回0,表示当前节点为根节点的高度为0
3、单层的逻辑
判断当前传入节点为根节点的二叉树是否为平衡二叉树:比较左子树高度和右子树高度,差值<=1返回当前二叉树的高度,否则返回-1.
class Solution {
public:int getDepth(TreeNode* node){if(node == nullptr) return 0;int leftDepth = getDepth(node->left);if(leftDepth == -1) return -1;int rightDepth = getDepth(node->right);if(rightDepth == -1) return -1;int result;if(abs(leftDepth - rightDepth) > 1)return -1;elseresult = 1 + max(leftDepth,rightDepth);return result;}bool isBalanced(TreeNode* root) {if(getDepth(root) != -1) return true;else return false;}
};
257. 二叉树的所有路径
递归三部曲+前序遍历+回溯pop+字符串处理即可。
class Solution {
public:void traversal(TreeNode* cur, vector<int>& path,vector<string>& result){path.push_back(cur->val);//到达叶子节点if(cur->left == nullptr && cur->right == nullptr){string sPath;for(int i = 0; i < path.size() - 1; i++){sPath += to_string(path[i]);sPath += "->";}sPath += to_string(path[path.size() - 1]);result.push_back(sPath);return;}//如果不是叶子节点if(cur->left){traversal(cur->left,path,result);path.pop_back(); //回溯}if(cur->right){traversal(cur->right,path,result);path.pop_back(); //回溯}}vector<string> binaryTreePaths(TreeNode* root) {vector<string> result;vector<int> path;if(root == nullptr) return result;traversal(root,path,result);return result;}
};
404. 左叶子之和
https://leetcode-cn.com/problems/sum-of-left-leaves/
如果某节点的左节点不为空,且左节点没有左右孩子,那么这个节点就是左叶子。
必须通过节点的父节点来判断其左孩子是不是左叶子。
递归三部曲:
1、返回值为数值之和,传入参数为树的根节点
2、终止条件:遇到空节点返回
3、单层逻辑:遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和和右子树左子叶之和,相加便是整个树的左叶子之和。
class Solution {
public:int sumOfLeftLeaves(TreeNode* root) {if(root == nullptr) return 0;int leftval = sumOfLeftLeaves(root->left);int rightval = sumOfLeftLeaves(root->right);int midval = 0;if(root->left != nullptr && root->left->left == nullptr && root->left->right == nullptr){midval = root->left->val;}int sum = midval + leftval + rightval;return sum;}
};
513. 找树左下角的值
https://leetcode-cn.com/problems/find-bottom-left-tree-value/
层序遍历+记录每一行的第一个节点数值即可。
112. 路径总和
https://leetcode-cn.com/problems/path-sum/
递归三部曲:
1、参数:二叉树的根节点+一个计数器
这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int
返回值:bool
2、终止条件
count初始值为目标和,然后每次减去遍历路径点的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
3、单层逻辑
由于终止条件是判断叶子节点,所以递归的过程中不要让空节点进入递归。
如果递归函数的返回值为true,说明找到了合适的路径,应该立即返回。
class Solution {
public:bool traversal(TreeNode* cur,int count){if(cur->left == nullptr && cur->right == nullptr && count == 0) return true; //遇到叶子节点,并且计数为0if(cur->left == nullptr && cur->right == nullptr) return false; //遇到叶子节点而没有找到合适的边,直接返回if(cur->left) {count -= cur->left->val;if(traversal(cur->left,count)) return true;count += cur->left->val;}if(cur->right) {count -= cur->right->val;if(traversal(cur->right,count)) return true;count += cur->right->val;}//否则return false;}bool hasPathSum(TreeNode* root, int targetSum) {if(root == nullptr) return false;targetSum = targetSum - root->val;return traversal(root,targetSum);}
};
二叉树修改与构造
226. 翻转二叉树
先序遍历,先交换左右孩子,然后反转左子树,反转右子树。
class Solution {
public:TreeNode* invertTree(TreeNode* root) {if(root == nullptr) return root;swap(root->left,root->right);invertTree(root->left);invertTree(root->right);return root;}
};
106. 从中序与后序遍历序列构造二叉树 比较复杂
https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
步骤:
1、如果数组大小为0的话,说明是空节点
2、如果不为空,取后序数组最后一个元素作为节点元素
3、找到后序数组最后一个元素在中序数组的位置,作为切割点
4、切割中序数组,切成中序左数组和中序右数组。(记住,先切割中序数组!!!)
5、切割后序数组,切成后序左数组和后序右数组
6、递归处理左区间和右区间
切割的时候按照左闭右开的原则。后序数组的切割要根据中序数组切割出来的大小,还要注意后序数组在切割前要将最后一个数剔除。
class Solution {
public:TreeNode* traversal(vector<int>& inorder,int Inbegin,int Inend,vector<int>& postorder,int Postbegin,int Postend){//第一步,检查后序数组大小if(Postbegin == Postend) return nullptr;//第二步:后序数组中最后一个元素,就是当前的中间节点int rootval = postorder[Postend - 1];TreeNode* root = new TreeNode(rootval);//叶子节点if(Postbegin - Postend == 1) return root;//第三步,中序数组找切割点int splitIndex = 0;for(splitIndex = Inbegin; splitIndex < Inend; splitIndex++){if(inorder[splitIndex] == rootval) break;}//第四步,切割中序数组,得到中序左数组和中序右数组int leftInbegin = Inbegin;int leftInend = splitIndex;int rightInbegin = splitIndex+1;int rightInend = Inend;//第五步:切割后序数组,得到后序左数组和后序右数组int leftPostbegin = Postbegin;int leftPostend = Postbegin + leftInend - leftInbegin;int rightPostbegin = leftPostend;int rightPostend = Postend - 1;//第六步,递归处理左区间和右区间root->left = traversal(inorder,leftInbegin,leftInend,postorder,leftPostbegin,leftPostend);root->right = traversal(inorder,rightInbegin,rightInend,postorder,rightPostbegin,rightPostend);return root;} TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {if(inorder.size() == 0 || postorder.size() == 0) return nullptr;//左闭右开return traversal(inorder,0,inorder.size(),postorder,0,postorder.size());}
};
105. 从前序与中序遍历序列构造二叉树,操作类似
https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/submissions/
654. 最大二叉树
https://leetcode-cn.com/problems/maximum-binary-tree/submissions/
二叉搜索树
700. 二叉搜索树中的搜索
递归:
TreeNode* searchBST(TreeNode* root, int val) {if(root == nullptr || root->val == val) return root;if(root->val > val) return searchBST(root->left,val);if(root->val > val) return searchBST(root->right,val);return nullptr;}
98. 验证二叉搜索树
二叉搜索树中序遍历得到的数组是递增的。
class Solution {
public:vector<int> vec;void traversal(TreeNode* node){if(node == nullptr) return ;traversal(node->left);vec.push_back(node->val);traversal(node->right);}bool isValidBST(TreeNode* root) {vec.clear();traversal(root);for(int i = 1; i < vec.size(); i++){if(vec[i] <= vec[i - 1]) return false;}return true;}
};
530. 二叉搜索树的最小绝对差
转换为有序数组,然后对有序数组进行比较。
501. 二叉搜索树中的众数
转换为有序数组,然后对相邻两个元素进行比较,统计相同频次,有一些细节需要注意。
class Solution {
public:vector<int> vec;void traversal(TreeNode* node){if(node == nullptr) return ;traversal(node->left);vec.push_back(node->val);traversal(node->right);}vector<int> findMode(TreeNode* root) {if(root == nullptr) return {};vec.clear();vector<int> result;traversal(root);unordered_map<int,int> umap;int maxcount = 1;int count = 1;result.push_back(vec[0]);for(int i = 1; i < vec.size(); i++){if(vec[i] == vec[i-1]){count++;}elsecount = 1;if(count == maxcount)result.push_back(vec[i]);if(count > maxcount){maxcount = count;result.clear();result.push_back(vec[i]);}}return result;}
};
538. 把二叉搜索树转换为累加树
https://leetcode-cn.com/problems/convert-bst-to-greater-tree/
定义一个全局变量preval用来存储上一个节点值,然后右中左遍历二叉树。中间节点处理就是加上preaval,然后更新preval。
公共祖先
236. 二叉树的最近公共祖先
https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/
通过后序遍历,回溯,自下向上。
如何判断一个节点是节点q和节点p的公共祖先:
如果找到一个节点,发现左子树出现了节点p,右子树出现了q;或者左子树出现q,右子树出现p。那么该节点就是节点p和q的最近公共祖先。
class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(root == p || root == q || root == nullptr) return root;TreeNode* left = lowestCommonAncestor(root->left,p,q);TreeNode* right = lowestCommonAncestor(root->right,p,q);//如果left和right都不为空,则说明root就是最近公共祖先if(left != nullptr && right != nullptr) return root;if(left == nullptr && right != nullptr) return right;else if(left != nullptr && right == nullptr) return left;else return nullptr;}
};
235. 二叉搜索树的最近公共祖先
https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/submissions/
class Solution {
public:TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q){if(cur == nullptr) return cur;if(cur->val > p->val && cur->val > q->val) {TreeNode* left = traversal(cur->left,p,q);if(left != nullptr) return left;}if(cur->val < p->val && cur->val < q->val){TreeNode* right = traversal(cur->right,p,q);if(right != nullptr) return right;}//否则说明,cur是最近公共祖先(从底向上)return cur;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {return traversal(root,p,q);}
};
二叉搜索树的修改与构造
701. 二叉搜索树中的插入操作
https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/
遍历二叉搜索树,然后遇到空节点,插入即可。递归函数返回节点。
class Solution {
public:TreeNode* insertIntoBST(TreeNode* root, int val) {if(root == nullptr){//插入操作TreeNode* node = new TreeNode(val);return node;}if(root->val > val) root->left = insertIntoBST(root->left,val);if(root->val < val) root->right = insertIntoBST(root->right,val);return root; }
};
450. 删除二叉搜索树中的节点
https://leetcode-cn.com/problems/delete-node-in-a-bst/
删除操作比较麻烦:
1、没有找到删除的结点,遍历到空结点直接返回。
2、找到了删除的结点:
【1】如果左右孩子都为空,直接删除这个结点,返回NULL为根结点
【2】如果左孩子为空,右孩子不为空,删除结点,右孩子补位,返回右孩子作为根结点
【3】如果右孩子为空,左孩子不为空,删除结点,左孩子补位,返回左孩子作为根结点
【4】如果左右孩子不为空,则将删除结点的左子树的头结点放到删除结点的右子树的最左边结点的左孩子上,返回删除结点右孩子,作为新的结点
class Solution {
public:TreeNode* deleteNode(TreeNode* root, int key) {if(root == nullptr) return root;//如果找到了该节点if(root->val == key) {//第二种情况,左右孩子均空,删除该节点,返回nullptrif(root->right == nullptr && root->left == nullptr) return nullptr;if(root->left == nullptr) return root->right;else if(root->right == nullptr) return root->left;else{TreeNode* cur = root->right; //找右子树最左边节点while(cur->left != nullptr)cur = cur->left;cur->left = root->left;TreeNode* tmp = root;root = root->right;delete tmp;return root;}}//如果没有找到,继续遍历if(root->val > key) root->left = deleteNode(root->left,key);if(root->val < key) root->right = deleteNode(root->right,key);return root;}
};
669. 修剪二叉搜索树
https://leetcode-cn.com/problems/trim-a-binary-search-tree/
如果当前结点的值小于low,要对该结点的右孩子进行再次搜索,直到找到满足区间的结点或者空结点,最后返回right结点
如果当前结点的值大于high,要对该结点的左孩子进行再次搜索,直到找到满足区间的结点或者空结点,最后返回left结点。
自上而下遍历检查,直到整棵树遍历完。
依次遍历结点左右孩子,并将返回的结果作为当前结点的左右孩子。
最后返回当前结点。
返回的结点必然是val在区间内或者是空结点。
class Solution {
public:TreeNode* trimBST(TreeNode* root, int low, int high) {if(root == nullptr) return nullptr;if(root->val < low){//寻找新的节点TreeNode* right = trimBST(root->right,low,high);return right;}if(root->val > high){TreeNode* left = trimBST(root->left,low,high);return left;}//如果在这个区间内,那么就对左右子树进行操作root->left = trimBST(root->left,low,high);root->right = trimBST(root->right,low,high);return root;}
};
108. 修剪二叉搜索树
https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/\
class Solution {
public:TreeNode* traversal(vector<int>& nums,int left,int right){//这里我们定义区间是左闭右开的if(left == right) return nullptr;int mid = left + ((right-left) >> 1);TreeNode* node = new TreeNode(nums[mid]);node->left = traversal(nums,left,mid);node->right = traversal(nums,mid+1,right);return node;}TreeNode* sortedArrayToBST(vector<int>& nums) {if(nums.empty()) return nullptr;return traversal(nums,0,nums.size());}
};