先简单介绍下刷题中常见的二叉树种类:满二叉树、完全二叉树、二叉搜索树、平衡二叉搜索树。
之前说过C++ STL中的set、multiset、map、multimap的底层实现是红黑树,其实也是一种平衡二叉搜索树,所以他们的增删操作的时间复杂度是O(logn)。
二叉树的定义
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
//听一些大佬说过,在面试的时候可能会让你在纸上写出二叉树节点的定义。
//平时在leetcode刷题的时候,节点是默认定义好的,这一点需要注意。
对于二叉树的前、中、后序遍历,在这里分别给出两种解法,递归和迭代。
144.二叉树的前序遍历
145.二叉树的后序遍历
94.二叉树的中序遍历
1、144.二叉树的前序遍历
//递归
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> vec;
traversal(root, vec);
return vec;
}
void traversal(TreeNode* node, vector<int>& vec) {
if (node != nullptr) {
vec.push_back(node->val);
traversal(node->left, vec);
traversal(node->right, vec);
}
}
};
//迭代
//之前有提到过函数的递归调用就是根据栈来实现的,那么也就可以用栈来模拟递归,写出迭代解法
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> vec;
if (root == nullptr) return vec;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
vec.push_back(node->val);//中
//这里是核心,前序遍历是中左右的顺序,那么在入栈时必须先右后左,出栈时才是先左后右
if (node->right != nullptr) st.push(node->right);//右
if (node->left != nullptr) st.push(node->left);//左
}
return vec;
}
};
2、145.二叉树的后序遍历
//递归
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> vec;
traversal(root, vec);
return vec;
}
void traversal(TreeNode* node, vector<int>& vec) {
if (node != nullptr) {
traversal(node->left, vec);
traversal(node->right, vec);
vec.push_back(node->val);
}
}
};
//迭代
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> vec;
if (root == nullptr) return vec;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
vec.push_back(node->val);//中
//核心,按左右顺序入栈,出栈后就是中右左,那么再反转vec,就是后序遍历顺序左右中
if (node->left != nullptr) st.push(node->left);//左
if (node->right != nullptr) st.push(node->right);//右
}
//反转vec得到后序遍历结果:左右中
reverse(vec.begin(), vec.end());
return vec;
}
};
3、94.二叉树的中序遍历
//递归
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> vec;
traversal(root, vec);
return vec;
}
void traversal(TreeNode* node, vector<int>& vec) {
if (node != nullptr) {
traversal(node->left, vec);
vec.push_back(node->val);
traversal(node->right, vec);
}
}
};
//迭代
//这里不能通过改变代码顺序来实现了
//因为前两个遍历过程中,先访问的节点是中间节点,先处理的节点也是中间节点
//但是,中序遍历的顺序是左中右,先访问的节点是中间节点,但先处理的节点是左节点
//所以需要借用指针的遍历来访问节点
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> vec;
TreeNode* cur = root;
while (cur != nullptr || !st.empty()) {
//用cur指针来访问节点,一直访问到最底层
if (cur != nullptr) {
st.push(cur);
cur = cur->left;//左
} else {
cur = st.top();
st.pop();
vec.push_back(cur->val);//中
cur = cur->right;//右
}
}
return vec;
}
};
先来一个小总结:树的前、中、后和层序遍历这四种遍历方式其实可以分为两大类——dfs(深度优先搜索)和bfs(广度优先搜索)。
深度优先就是从头一直走到底。
广度优先就是一层一层,或者是一圈一圈的来。
102.二叉树的层序遍历
107.二叉树的层序遍历II
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的层序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针II
1、102.二叉树的层序遍历(可以作为层序遍历相关题目的模板)
//层序遍历是利用了队列一层一层的遍历,也就是先遍历一层,再遍历下一层
//那这不就是队列先进先出的思想吗,所以用队列来实现层序遍历
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
vector<vector<int>> ret;
if (root == nullptr) return ret;
que.push(root);
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 != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
ret.push_back(vec);
}
return ret;
}
};
2、107.二叉树的层序遍历II
//和上一道题区别不大,最后反转一下数组就行
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
queue<TreeNode*> que;
vector<vector<int>> ret;
if (root == nullptr) return ret;
que.push(root);
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 != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
ret.push_back(vec);
}
reverse(ret.begin(), ret.end());
return ret;
}
};
3、199.二叉树的右视图
//与前面的题类似,不过只需要把每层最后一个节点值放入数组
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
queue<TreeNode*> que;
vector<int> ret;
if (root == nullptr) return ret;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
que.pop();
//把每层的最后一个节点值放入
if (i == (size - 1)) ret.push_back(node->val);
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
}
return ret;
}
};
4、637.二叉树的层平均值
//每层节点值求和再除以节点个数就行
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
queue<TreeNode*> que;
vector<double> ret;
if (root == nullptr) return ret;
que.push(root);
while (!que.empty()) {
int size = que.size();
double sum = 0;
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
que.pop();
//每层节点值求和再除以节点个数
sum += node->val;
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
ret.push_back(sum / size);
}
return ret;
}
};
5、429.N叉树的层序遍历
//与二叉树的层序遍历差别不大,只要让多个孩子节点都入队就行
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
queue<Node*> que;
vector<vector<int>> ret;
if (root == nullptr) return ret;
que.push(root);
while (!que.empty()) {
int size = que.size();
vector<int> vec;
for (int i = 0; i < size; ++i) {
Node* node = que.front();
que.pop();
vec.push_back(node->val);
//让所有孩子节点入队
for (int j = 0; j < node->children.size(); ++j) {
que.push(node->children[j]);
}
}
ret.push_back(vec);
}
return ret;
}
};
6、515.在每个树行中找最大值
//在遍历每一层时找到最大值即可
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
queue<TreeNode*> que;
vector<int> ret;
if (root == nullptr) return ret;
que.push(root);
while (!que.empty()) {
int size = que.size();
int maxVal = INT_MIN;
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
que.pop();
maxVal = max(maxVal, node->val);
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
ret.push_back(maxVal);
}
return ret;
}
};
7、116.填充每个节点的下一个右侧节点指针
//按照层序遍历的框架去写就行
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if (root == nullptr) return nullptr;
que.push(root);
while (!que.empty()) {
int size = que.size();
Node* preNode;
Node* node;
for (int i = 0; i < size; ++i) {
//取出本层第一个节点
if (i == 0) {
preNode = que.front();
que.pop();
node = preNode;
} else {
node = que.front();
que.pop();
preNode->next = node;//本层前一个节点指向本节点
preNode = preNode->next;
}
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
//本层最后一个节点指向nullptr
preNode->next = nullptr;
}
return root;
}
};
8、117.填充每个节点的下一个右侧节点指针II
//跟上一题一模一样的代码...
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if (root == nullptr) return root;
que.push(root);
while (!que.empty()) {
int size = que.size();
Node* preNode;
Node* node;
for (int i = 0; i < size; ++i) {
if (i == 0) {
preNode = que.front();
que.pop();
node = preNode;
} else {
node = que.front();
que.pop();
preNode->next = node;
preNode = preNode->next;
}
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
preNode->next = nullptr;
}
return root;
}
};
再来个小总结:层序遍历几乎都是一样的套路,只需要根据题意进行微调就行。
101.对称二叉树
104.二叉树的最大深度
559.N叉树的最大深度
111.二叉树的最小深度
222.完全二叉树的节点个数
110.平衡二叉树
257.二叉树的所有路径
404.左叶子之和
513.找树左下角的值
112.路径总和
113.路径总和II
1、101.对称二叉树
//递归
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == nullptr) return true;
return isSame(root->left, root->right);
}
//递归三步:
//1、确定递归函数的参数和返回值
bool isSame(TreeNode* tree1, TreeNode* tree2) {
//2、确定终止条件
if (tree1 == nullptr && tree2 != nullptr) return false;
else if (tree1 != nullptr && tree2 == nullptr) return false;
else if (tree1 == nullptr && tree2 == nullptr) return true;
//先排除空节点,再比较数值
else if (tree1->val != tree2->val) return false;
//3、单层递归的逻辑
//左节点的左孩子跟右节点的右孩子比,左节点的右孩子跟右节点的左孩子比
bool same = isSame(tree1->left, tree2->right) && isSame(tree1->right, tree2->left);
return same;
}
};
//迭代
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == nullptr) return true;
queue<TreeNode*> que;
que.push(root->left);
que.push(root->right);
while (!que.empty()) {
TreeNode* leftNode = que.front();
que.pop();
TreeNode* rightNode = que.front();
que.pop();
//左右节点都为空,说明为true,continue
if (leftNode == nullptr && rightNode == nullptr) continue;
//如果左右节点一个不为空,或两个都不为空但值不相等,返回false
if (leftNode == nullptr || rightNode == nullptr || leftNode->val != rightNode->val) {
return false;
}
que.push(leftNode->left);
que.push(rightNode->right);
que.push(leftNode->right);
que.push(rightNode->left);
}
return true;
}
};
//使用栈,这道题的迭代法其实就是把左右子树要比较的元素顺序放进一个容器中,然后再成对取出比较即可,因此也可以用栈来实现
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == nullptr) return true;
stack<TreeNode*> st;
st.push(root->left);
st.push(root->right);
while (!st.empty()) {
TreeNode* leftNode = st.top();
st.pop();
TreeNode* rightNode = st.top();
st.pop();
//左右节点都为空,说明为true,continue
if (leftNode == nullptr && rightNode == nullptr) continue;
//如果左右节点一个不为空,或两个都不为空但值不相等,返回false
if (leftNode == nullptr || rightNode == nullptr || leftNode->val != rightNode->val) {
return false;
}
st.push(leftNode->left);
st.push(rightNode->right);
st.push(leftNode->right);
st.push(rightNode->left);
}
return true;
}
};
2、104.二叉树的最大深度
//递归
class Solution {
public:
int maxDepth(TreeNode* root) {
return getMaxDepth(root);
}
//递归三步:
//1、确定递归函数的参数和返回值,参数就是树的节点,返回值是树的深度
int getMaxDepth(TreeNode* node) {
//2、确定终止条件
if (node == nullptr) return 0;
//3、确定单层递归的逻辑
//先取左子树深度,再取右子树深度,最后取左右子树深度的最大值+1
int leftDepth = getMaxDepth(node->left);
int rightDepth = getMaxDepth(node->right);
int maxDepth = max(leftDepth, rightDepth) + 1;
return maxDepth;
}
};
//迭代
//因为最大深度就是求层数,所以参考层序遍历
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0;
queue<TreeNode*> que;
que.push(root);
int ret = 0;
while (!que.empty()) {
int size = que.size();
++ret;
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
que.pop();
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
}
return ret;
}
};
3、559.N叉树的最大深度
//递归
class Solution {
public:
int maxDepth(Node* root) {
return getMaxDepth(root);
}
//递归三步:
//1、确定递归函数的参数和返回值
int getMaxDepth(Node* node) {
//2、确定终止条件
if (node == nullptr) return 0;
//3、确定单层递归逻辑:找到所有孩子节点的最大深度,然后+1返回
int depth = 0;
for (int i = 0; i < node->children.size(); ++i) {
depth = max(depth, getMaxDepth(node->children[i]));
}
return depth + 1;
}
};
//迭代
//同样是层序遍历
class Solution {
public:
int maxDepth(Node* root) {
if (root == nullptr) return 0;
queue<Node*> que;
que.push(root);
int ret = 0;
while (!que.empty()) {
int size = que.size();
++ret;
for (int i = 0; i < size; ++i) {
Node* node = que.front();
que.pop();
for (int j = 0; j < node->children.size(); ++j) {
que.push(node->children[j]);
}
}
}
return ret;
}
};
4、111.二叉树的最小深度
//递归
//这里和最大深度有些区别
//比如:当根节点没有左孩子(或右孩子)时,这时的最小深度不是1,而是除了为空的左孩子(或右孩子)后的最小深度
//也就是当左右孩子都为空时,才是最小深度
class Solution {
public:
int minDepth(TreeNode* root) {
return getMinDepth(root);
}
//递归三步
//1、确定递归函数参数及返回值
int getMinDepth(TreeNode* node) {
//2、确定递归的终止条件
if (node == nullptr) return 0;
//3、确定单层递归的逻辑
int leftDepth = getMinDepth(node->left);
int rightDepth = getMinDepth(node->right);
//这里和最大深度不同
//当一个左子树为空,右不为空,这是并不是最低点
if (node->left == nullptr && node->right != nullptr) return rightDepth + 1;
//当一个右子树为空,左不为空,这是并不是最低点
if (node->left != nullptr && node->right == nullptr) return leftDepth + 1;
int ret = min(leftDepth, rightDepth) + 1;
return ret;
}
};
//迭代
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == nullptr) return 0;
queue<TreeNode*> que;
que.push(root);
int ret = 0;
while (!que.empty()) {
int size = que.size();
++ret;
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
que.pop();
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
//当左右孩子都为空时,说明找到了最小深度,直接返回
if (node->left == nullptr && node->right == nullptr) return ret;
}
}
return ret;
}
};
5、222.完全二叉树的节点个数
//递归
class Solution {
public:
int countNodes(TreeNode* root) {
return getCountNodes(root);
}
//递归三步
//1、确定递归函数参数及返回值
int getCountNodes(TreeNode* node) {
//2、确定终止条件
if (node == nullptr) return 0;
//3、确定单层递归逻辑
int leftCount = getCountNodes(node->left);
int rightCount = getCountNodes(node->right);
return leftCount + rightCount + 1;
}
};
//迭代
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr) return 0;
queue<TreeNode*> que;
que.push(root);
int count = 0;
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
++count;
TreeNode* node = que.front();
que.pop();
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
}
return count;
}
};
6、110.平衡二叉树
//递归
class Solution {
public:
bool isBalanced(TreeNode* root) {
if (getDepth(root) == -1) return false;
return true;
}
//递归三步
//1、确定递归函数参数及返回值,返回-1说明不是平衡二叉树
int getDepth(TreeNode* node) {
//2、确定终止条件
if (node == nullptr) return 0;
//3、确定单层递归逻辑
//分别求左右两子树的深度,如果差值大于1,返回-1,否则返回1
int leftDepth = getDepth(node->left);
//说明左子树不是平衡二叉树,整个树也就不是平衡二叉树了
if (leftDepth == -1) return -1;
int rightDepth = getDepth(node->right);
//说明右子树不是平衡二叉树
if (rightDepth == -1) return -1;
//左右子树的高度相差大于1,说明不是平衡二叉树
if (abs(leftDepth - rightDepth) > 1) return -1;
return max(leftDepth, rightDepth) + 1;
}
};
//迭代
注意这里没法用层序遍历!!!层序遍历只能用来求深度,不能用来求高度,是有区别的。
要想用迭代,只能用栈来模拟,但是用栈来实现有些复杂(还没理解透),所以就不放迭代的代码了。
7、257.二叉树的所有路径
//递归
//找所有的路径,就可以考虑回溯的思想,这里就会用到回溯
//关于回溯会在以后详细总结
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if (root == nullptr) return result;
backTracking(root, path, result);
return result;
}
//递归三步
//1、确定递归函数的参数及返回值
void backTracking(TreeNode* node, vector<int>& path, vector<string>& result) {
path.push_back(node->val);
//2、确定终止条件:当到叶子节点时,说明没有路可以走了
if (node->left == nullptr && node->right == nullptr) {
string tempPath = "";
for (int i = 0; i < path.size() - 1; ++i) {
tempPath += to_string(path[i]) + "->";
}
tempPath += to_string(path[path.size() - 1]);
//收集一个路径
result.push_back(tempPath);
return;
}
//3、确定单层递归的逻辑
if (node->left != nullptr) {
backTracking(node->left, path, result);
//这一步就是回溯的核心
path.pop_back();
}
if (node->right != nullptr) {
backTracking(node->right, path, result);
//这一步就是回溯的核心
path.pop_back();
}
}
};
8、404.左叶子之和
//递归
//这道题其实比较绕,可能第一眼想到的是层序(我就是),但其实没法用层序
//因为必须通过父节点才能判断其左孩子节点是不是左叶子节点
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
return getSum(root);
}
//递归三步
//1、确定递归函数的参数及返回值
int getSum(TreeNode* node) {
//2、确定终止条件
if (node == nullptr) return 0;
//3、确定单次递归的逻辑:
//遇到左叶子节点时,先记录其值,然后递归计算其左子树左叶子节点和和右子树左叶子节点和
int val = 0;
if (node->left != nullptr && node->left->left == nullptr && node->left->right == nullptr) {
val = node->left->val;
}
int leftVal = getSum(node->left);
int rightVal = getSum(node->right);
return val + leftVal + rightVal;
}
};
//迭代
//这里同样没法用层序遍历,因为不知道是不是左叶子节点
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == nullptr) return 0;
stack<TreeNode*> st;
st.push(root);
int ret = 0;
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
if (node->left != nullptr && node->left->left == nullptr && node->left->right == nullptr) {
ret += node->left->val;
}
//这里写的是前序遍历,写中序后序遍历都行
if (node->left != nullptr) st.push(node->left);
if (node->right != nullptr) st.push(node->right);
}
return ret;
}
};
9、513.找树左下角的值
//递归
//这道题的递归不容易理解,其实第一眼看到这个题,想到的应该是层序
class Solution {
public:
int maxLen = INT_MIN; //记录最大深度
int maxLeftVal = 0; //记录最大深度最左侧节点的数值
int findBottomLeftValue(TreeNode* root) {
backTracking(root, 0);
return maxLeftVal;
}
//递归三步
//1、确定递归函数的参数及返回值,因为要遍历整棵树,所以递归函数没有返回值
void backTracking(TreeNode* node, int leftLen) {
//2、确定递归终止条件
if (node->left == nullptr && node->right == nullptr) {
if (maxLen < leftLen) {
maxLen = leftLen;
maxLeftVal = node->val;
}
return;
}
//3、确定单次递归的逻辑
if (node->left) {
++leftLen;
backTracking(node->left, leftLen);
//回溯
--leftLen;
}
if (node->right) {
++leftLen;
backTracking(node->right, leftLen);
//回溯
--leftLen;
}
}
};
//迭代
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
if (root == nullptr) return 0;
queue<TreeNode*> que;
que.push(root);
int ret = 0;
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
que.pop();
//记录每一层最左边的值
if (i == 0) ret = node->val;
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
}
return ret;
}
};
10、112.路径总和
//递归
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr) return false;
return backTracking(root, targetSum - root->val);
}
//递归三步
//1、确定递归函数的参数及返回值
//在遍历过程中只用找到一条路径就行,所以递归函数需要有返回值
bool backTracking(TreeNode* node, int count) {
//2、确定终止条件
//遇到叶子节点并且count==0,返回true
if (node->left == nullptr && node->right == nullptr && count == 0) return true;
//遇到叶子节点,但计数值不为0,返回false
if (node->left == nullptr && node->right == nullptr) return false;
//3、确定单次递归的逻辑
if (node->left != nullptr) {
count -= node->left->val;
if (backTracking(node->left, count)) return true;
//回溯
count += node->left->val;
}
if (node->right != nullptr) {
count -= node->right->val;
if (backTracking(node->right, count)) return true;
//回溯
count += node->right->val;
}
return false;
}
};
//更简洁的递归代码
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr) return false;
if (root->left == nullptr && root->right == nullptr) return targetSum == root->val;
return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}
};
11、113.路径总和II
//递归
class Solution {
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<vector<int>> ret;
vector<int> path;
if (root == nullptr) return ret;
path.push_back(root->val);
backTracking(root, ret, path, targetSum - root->val);
return ret;
}
//递归三步
//1、确定递归函数及返回值
void backTracking(TreeNode* node, vector<vector<int>>& ret, vector<int>& path, int targetSum) {
//2、确定终止条件
if (node->left == nullptr && node->right == nullptr && targetSum == 0) {
ret.push_back(path);
return;
}
if (node->left == nullptr && node->right == nullptr) return;
//3、确定单层递归的逻辑
if (node->left != nullptr) {
path.push_back(node->left->val);
backTracking(node->left, ret, path, targetSum - node->left->val);
path.pop_back();
}
if (node->right != nullptr) {
path.push_back(node->right->val);
backTracking(node->right, ret, path, targetSum - node->right->val);
path.pop_back();
}
}
};
小总结:在112.路径总和和113.路径总和II这两道题中说明了当需要遍历整个树时,递归函数不需要返回值;在只需要找到一个答案,不需要遍历整个树时,递归函数需要有返回值。
226.反转二叉树
106.从中序与后续遍历序列构造二叉树
105.从前序与中序遍历序列构造二叉树
654.最大二叉树
617.合并二叉树
701.二叉搜索树中的插入操作
450.删除二叉搜索树中的节点
669.修剪二叉搜索树
108.将有序数组转换为二叉搜索树
1、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;
}
};
//迭代
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> que;
if (root == nullptr) return root;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
que.pop();
//交换左右孩子节点
swap(node->left, node->right);
if (node->left != nullptr) que.push(node->left);
if (node->right != nullptr) que.push(node->right);
}
}
return root;
}
};
2、106.从中序与后续遍历序列构造二叉树
//根据中序与后续的特点,想好怎么分割两个数组就好
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.size() == 0 || postorder.size() == 0) return nullptr;
TreeNode* ret = new TreeNode(postorder[postorder.size() - 1]);
for (int i = 0; i < inorder.size(); ++i) {
if (inorder[i] == postorder[postorder.size() - 1]) {
vector<int> inorderLeft(inorder.begin(), inorder.begin() + i);
vector<int> inorderRight(inorder.begin() + i + 1, inorder.end());
vector<int> postorderLeft(postorder.begin(), postorder.begin() + i);
vector<int> postorderRight(postorder.begin() + i, postorder.end() - 1);
ret->left = buildTree(inorderLeft, postorderLeft);
ret->right = buildTree(inorderRight, postorderRight);
}
}
return ret;
}
};
3、105.从前序与中序遍历序列构造二叉树
//跟上一题类似,根据前序与中序的特点分割数组
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (preorder.size() == 0 || inorder.size() == 0) return nullptr;
TreeNode* ret = new TreeNode(preorder[0]);
for (int i = 0; i < inorder.size(); ++i) {
if (inorder[i] == preorder[0]) {
vector<int> preorderLeft(preorder.begin() + 1, preorder.begin() + 1 + i);
vector<int> preorderRight(preorder.begin() + 1 + i, preorder.end());
vector<int> inorderLeft(inorder.begin(), inorder.begin() + i);
vector<int> inorderRight(inorder.begin() + i + 1, inorder.end());
ret->left = buildTree(preorderLeft, inorderLeft);
ret->right = buildTree(preorderRight, inorderRight);
}
}
return ret;
}
};
小总结:根据前序与中序、中序与后序都可以唯一确定一颗二叉树,但是根据前序与后序不能唯一确定一颗二叉树,因为没有中序无法确定左右部分,也就是无法分割。
4、654.最大二叉树
//递归
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return func(nums);
}
//递归三步
//1、确定递归函数及返回值
TreeNode* func(vector<int>& nums) {
//2、确定终止条件:当nums.size==1时说明遍历到了叶子节点,可以返回
TreeNode* node = new TreeNode(0);
if (nums.size() == 1) {
node->val = nums[0];
return node;
}
//3、确定单次递归的逻辑:找到数组中的最大值下标,下标左侧对应左子树,右侧对应右子树
int maxVal = 0;
int maxValIndex = 0;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] > maxVal) {
maxVal = nums[i];
maxValIndex = i;
}
}
node->val = maxVal;
//最大值所在下标的左侧构造左子树
if (maxValIndex > 0) {
vector<int> leftNums(nums.begin(), nums.begin() + maxValIndex);
node->left = func(leftNums);
}
//最大值所在下标的右侧构造右子树
if (maxValIndex < nums.size() - 1) {
vector<int> rightNums(nums.begin() + maxValIndex + 1, nums.end());
node->right = func(rightNums);
}
return node;
}
};
5、617.合并二叉树
//递归
//其实和遍历一个树一样
class Solution {
public:
//递归三步
//1、确定递归函数参数及返回值
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
//2、确定终止条件
if (root1 == nullptr) return root2;
if (root2 == nullptr) return root1;
//3、确定单次递归逻辑
root1->val += root2->val;
root1->left = mergeTrees(root1->left, root2->left);
root1->right = mergeTrees(root1->right, root2->right);
return root1;
}
};
//迭代
//层序遍历
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (root1 == nullptr) return root2;
if (root2 == nullptr) return root1;
queue<TreeNode*> que;
que.push(root1);
que.push(root2);
while (!que.empty()) {
TreeNode* node1 = que.front();
que.pop();
TreeNode* node2 = que.front();
que.pop();
node1->val += node2->val;
//如果左子树都不为空,入队
if (node1->left != nullptr && node2->left != nullptr) {
que.push(node1->left);
que.push(node2->left);
}
//如果右子树都不为空,入队
if (node1->right != nullptr && node2->right != nullptr) {
que.push(node1->right);
que.push(node2->right);
}
//如果root1的左节点为空,root2的左节点不为空,直接赋值过去
if (node1->left == nullptr && node2->left != nullptr) {
node1->left = node2->left;
}
//如果root1的右节点为空,root2的右节点不为空,直接赋值过去
if (node1->right == nullptr && node2->right != nullptr) {
node1->right = node2->right;
}
}
return root1;
}
};
6、701.二叉搜索树中的插入操作
//递归
class Solution {
public:
//递归三步:
//1、确定递归函数参数及返回值
TreeNode* insertIntoBST(TreeNode* root, int val) {
//2、确定终止条件
if (root == nullptr) {
TreeNode* node = new TreeNode(val);
return node;
}
//3、单次递归逻辑
if (val < root->val) {
root->left = insertIntoBST(root->left, val);
} else {
root->right = insertIntoBST(root->right, val);
}
return root;
}
};
//迭代
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val);
TreeNode* cur = root;
TreeNode* parent = root;//记录cur的上一个节点,否则无法赋值
while (cur != nullptr) {
parent = cur;
if (val < cur->val) {
cur = cur->left;
} else {
cur = cur->right;
}
}
TreeNode* node = new TreeNode(val);
if (val < parent->val) {
parent->left = node;
} else {
parent->right = node;
}
return root;
}
};
7、450.删除二叉搜索树中的节点
//递归
class Solution {
public:
//递归三步:
//1、确定递归函数参数及返回值
TreeNode* deleteNode(TreeNode* root, int key) {
//2、确定终止条件
//第一种情况:没找到删除节点,遍历到空后直接返回
if (root == nullptr) return root;
//3、确定单次递归逻辑
if (root->val == key) {
//第二种情况:左右孩子都为空,直接删除返回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* temp = root;
root = root->right;
delete temp;
return root;
}
}
if (root->val > key) root->left = deleteNode(root->left, key);
if (root->val < key) root->right = deleteNode(root->right, key);
return root;
}
};
8、669.修剪二叉搜索树
//递归
class Solution {
public:
//递归三步:
//1、确定递归函数参数及返回值
TreeNode* trimBST(TreeNode* root, int low, int high) {
//2、确定终止条件
if (root == nullptr) return root;
//3、确定单次递归逻辑
if (root->val < low) {
//如果当前节点元素小于low,那么递归其右子树,返回右子树符合条件的头节点
TreeNode* right = trimBST(root->right, low, high);
return right;
}
if (root->val > high) {
//如果当前节点元素大于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;
}
};
//迭代
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if (root == nullptr) return root;
//处理头节点,让头节点移动到[low,high]范围内
while (root != nullptr && (root->val < low || root->val > high)) {
if (root->val < low) root = root->right;//小于low往右
else root = root->left;//大于high往左
}
TreeNode* cur = root;
//此时root已经在[low,high]内,处理左孩子元素小于low的情况
while (cur != nullptr) {
while (cur->left != nullptr && cur->left->val < low) {
cur->left = cur->left->right;
}
cur = cur->left;
}
cur = root;
//此时root已经在[low,high]内,处理右孩子元素大于high的情况
while (cur != nullptr) {
while (cur->right != nullptr && cur->right->val > high) {
cur->right = cur->right->left;
}
cur = cur->right;
}
return root;
}
};
9、108.将有序数组转换为二叉搜索树
//递归
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
if (nums.size() == 0) return nullptr;
return traversal(nums, 0, nums.size() - 1);
}
//递归三步:
//1、确定递归函数及返回值
TreeNode* traversal(vector<int>& nums, int left, int right) {
//2、确定终止条件
if (left > right) return nullptr;
//3、确定单次递归逻辑
int mid = left + (right - left) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = traversal(nums, left, mid - 1);
root->right = traversal(nums, mid + 1, right);
return root;
}
};
700.二叉搜索树中的搜索
98.验证二叉搜索树
530.二叉搜索树的最小绝对值
501.二叉搜索树中的众数
1、700.二叉搜索树中的搜索
//递归
class Solution {
public:
//递归三步
//1、确定递归函数参数及返回值
TreeNode* searchBST(TreeNode* root, int val) {
//2、确定终止条件
if (root == nullptr || root->val == val) return root;
//3、确定单次递归的逻辑
else if (root->val < val) return searchBST(root->right, val);
else if (root->val > val) return searchBST(root->left, val);
else return NULL;
}
};
//迭代
//通常用栈或队列进行迭代,但这道题不用,可直接利用二叉搜索树的性质进行迭代
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
//类似二分查找
while (root != nullptr) {
if (root->val == val) return root;
else if (root->val < val) root = root->right;
else root = root->left;
}
return NULL;
}
};
2、98.验证二叉搜索树
//递归
//验证二叉搜索树,就相当于判断中序遍历是不是递增的,所以在中序遍历基础上修改
class Solution {
public:
TreeNode* pre = nullptr;//记录前一个节点
//递归三步
//1、确定递归函数参数及返回值
bool isValidBST(TreeNode* root) {
//2、确定终止条件
if (root == nullptr) return true;
//3、单次递归的逻辑,修改中序遍历
bool left = isValidBST(root->left);
if (pre != nullptr && pre->val >= root->val) return false;
pre = root;//记录前一个节点
bool right = isValidBST(root->right);
return left && right;
}
};
//迭代
//用栈进行中序遍历
class Solution {
public:
bool isValidBST(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* pre = nullptr;
while (cur != nullptr || !st.empty()) {
if (cur != nullptr) {
st.push(cur);
cur = cur->left;//左
} else {
cur = st.top();//中
st.pop();
if (pre != nullptr && cur->val <= pre->val) return false;
pre = cur;
cur = cur->right;//右
}
}
return true;
}
};
3、530.二叉搜索树的最小绝对值
//递归
//二茬搜索树就是一个有序数组,其实就是在有序数组上找最小差(相邻元素求差找最小)
class Solution {
public:
int ret = INT_MAX;//记录最小差
TreeNode* pre = nullptr;//记录上一个节点
int getMinimumDifference(TreeNode* root) {
inorder(root);
return ret;
}
//在中序遍历基础上修改即可
void inorder(TreeNode* node) {
if (node == nullptr) return;
inorder(node->left);
if (pre != nullptr) ret = min(ret, node->val - pre->val);
pre = node;
inorder(node->right);
}
};
//迭代
//栈模拟中序遍历
class Solution {
public:
int getMinimumDifference(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* pre = nullptr;
int ret = INT_MAX;
while (cur != nullptr || !st.empty()) {
if (cur != nullptr) {
st.push(cur);
cur = cur->left;//左
} else {
cur = st.top();
st.pop();
if (pre != nullptr) ret = min(ret, cur->val - pre->val);//中
pre = cur;
cur = cur->right;//右
}
}
return ret;
}
};
4、501.二叉搜索树中的众数
//递归
class Solution {
public:
vector<int> findMode(TreeNode* root) {
inorder(root);
return ret;
}
private:
int maxCount = 0;
int count = 0;
TreeNode* pre = nullptr;
vector<int> ret;
//同样使用中序遍历
void inorder(TreeNode* node) {
if (node == nullptr) return;
inorder(node->left);//左
//中
if(pre == nullptr) { //第一个节点
count = 1;
} else if (pre->val == node->val) { //与前一个节点值相同
++count;
} else { //与前一个节点值不同
count = 1;
}
pre = node;
if (count == maxCount) {
ret.push_back(node->val);
}
if (count > maxCount) {
maxCount = count;
ret.clear();
ret.push_back(node->val);
}
inorder(node->right);//右
}
};
//迭代
class Solution {
public:
vector<int> findMode(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* pre = nullptr;
vector<int> ret;
int maxCount = 0;
int count = 0;
while (cur != nullptr || !st.empty()) {
if (cur != nullptr) {
st.push(cur);
cur = cur->left;//左
} else {
cur = st.top();
st.pop();
if (pre == nullptr) count = 1;
else if (pre->val == cur->val) ++count;
else count = 1;
if (count == maxCount) ret.push_back(cur->val);
if (count > maxCount) {
maxCount = count;
ret.clear();
ret.push_back(cur->val);
}
pre = cur;
cur = cur->right;//右
}
}
return ret;
}
};
小总结:跟二叉搜索树相关的都可以用中序遍历,这样遍历后的数组是按升序排列的,方便操作。
236.二叉树的最近公共祖先
235.二叉搜索树的最近公共祖先
1、236.二叉树的最近公共祖先
//这道题首先要想到自底向上查找,这样就可以找到公共祖先了,
//二叉树的自底向上查找就是回溯,也就是后序遍历
//递归
class Solution {
public:
//递归三步
//1、确定递归函数参数及返回值
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//2、确定终止条件
if (root == p || root == q || root == NULL) return root;
//3、确定单次递归逻辑
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left != NULL && right != NULL) return root;
if (left == NULL && right != NULL) return right;
else if (left != NULL && right == NULL) return left;
else return NULL;
}
};
小总结:
1、求最小公共祖先,需要自底向上遍历,对于二叉树来说就是后序遍历(回溯)。
2、之前说过遍历整个树不需要返回值,但在236题中需要将找到的结果返回,也就是left和right,因为需要进行逻辑判断。
3、如果递归函数有返回值,那需要区分是搜索一条边,还是搜索整个树。
// 搜索一条边:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
// 搜索整个树:
left = 递归函数(root->left);
right = 递归函数(root->right);
显然上一道题就是需要搜索整个树,所以用的是第二种写法。
2、235.二叉搜索树的最近公共祖先
//递归
class Solution {
public:
//递归三步
//1、确定递归函数参数及返回值
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//2、确定终止条件
if (root == NULL) return root;
//3、确定单次递归逻辑 这里是标准的搜素一条边
if (root->val > p->val && root->val > q->val) {
TreeNode* left = lowestCommonAncestor(root->left, p, q);
if (left != NULL) return left;
}
if (root->val < p->val && root->val < q->val) {
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (right != NULL) return right;
}
return root;
}
};
//迭代
//直接利用二叉搜索树的性质
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
while (root != NULL) {
if (root->val > p->val && root->val > q->val) {
root = root->left;
} else if (root->val < p->val && root->val < q->val) {
root = root->right;
} else return root;
}
return NULL;
}
};
二叉树是一种很重要的数据结构,感觉在面试笔试中也是会被经常问道的一个点,所以也对二叉树常见题目进行了比较详细的分类总结。看了上面的题目,其实也不难发现跟二叉树相关的题目总会跟递归扯上关系。听过一句很有意思的话,“一入递归深似海,从此offer是路人”,递归的代码通常很简洁,但是却一写就废,所以这里总结一下递归的三个步骤。
递归三步:
1、确定递归函数的参数和返回值:确定哪些参数是递归的过程中需要处理的,那么就在递归函数中加入这个参数,明确每次递归的返回值是什么从而确定递归函数的返回值。
2、确定终止条件:在递归过程中如果出现栈溢出的情况,通常是终止条件没有确定好。
3、确定单层递归的逻辑:确定每一层递归需要处理的信息(需要重复调用的部分)。