思路:
- 明确一个节点要做的事情,剩下的事抛给框架,
- 有些需要辅助函数来递归,如比较两个节点的,要求深度的
- 遍历整棵树不需要返回值,遍历某一路径则需要
- 平衡⼆叉搜索树:⼜被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是⼀棵空树或它的左右两个⼦树的⾼度差的绝对值不超过1,并且左右两个⼦树都是⼀棵平衡⼆叉树。
- C++中map、set、multimap,multiset的底层实现都是平衡⼆叉搜索树,所以map、set的增删操作时间时间复杂度是logn,注意我这⾥没有说unordered_map、unordered_set,unordered_map、unordered_map底层实现是哈希表。
所以使⽤⾃⼰熟悉的编程语⾔写算法,⼀定要知道常⽤的容器底层都是如何实现的,最基本的就是map、set等等,否则⾃⼰写的代码,⾃⼰对其性能分析都分析不清楚!
- 二叉树是以二叉树定义二叉树的数据结构;天然具有递归性质
- 递归三部曲:返回值和参数;递归终止条件;单层递归过程(递归逻辑);
0、二叉树遍历方式
⼆叉树主要有两种遍历⽅式:
- 深度优先遍历:先往深⾛,遇到叶⼦节点再往回⾛。
这⾥前中后,其实指的就是中间节点的遍历顺序
前序遍历:中左右
中序遍历:左中右
后序遍历:左右中 - ⼴度优先遍历:⼀层⼀层的去遍历。
- 见前一章:链接
1、二叉树属性——搞定其一,剩下遍历
【104. 二叉树的最大深度】
- 递归
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==nullptr) return 0;
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return max(leftDepth,rightDepth)+1;
}
};
- 迭代(就是层序遍历,见第五章笔记)
【111. 二叉树的最小深度】
- 递归法(dfs)
class Solution {
public:
int minDepth(TreeNode* root) {
if(!root) return 0;
int _l = minDepth(root->left);
int _r = minDepth(root->right);
if(_l==0 && _r==0) return 1;
if(_l==0) return _r+1;
if(_r==0) return _l+1;
return min(_l,_r)+1;
}
};
- 迭代法/bfs 也可以用染色法
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == NULL) return 0;
int depth = 0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()) {
int size = que.size();
depth++; // 记录最小深度
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
if (!node->left && !node->right) { // 当左右孩子都为空的时候,说明是最低点的一层了,退出
return depth;
}
}
}
return depth;
}
};
【100. 相同的树】
- dfs
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if(p==nullptr&&q==nullptr) return true;
if(p==nullptr||q==nullptr) return false;
if(q->val != p->val) return false;
return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
}
};
- bfs
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr && q == nullptr) {
return true;
} else if (p == nullptr || q == nullptr) {
return false;
}
stack<TreeNode*> s1,s2;
s1.push(p);s2.push(q);
while(!s1.empty() and !s2.empty()){
TreeNode* cur1 = s1.top();
TreeNode* cur2 = s2.top();
if(cur1->val != cur2->val) return false;
s1.pop();s2.pop();
if ((cur1->left == nullptr) ^ (cur2->left == nullptr)) {
return false;
}
if ((cur1->right == nullptr) ^ (cur2->right == nullptr)) {
return false;
}
if(cur1->left) s1.push(cur1->left);
if(cur1->right) s1.push(cur1->right);
if(cur2->left) s2.push(cur2->left);
if(cur2->right) s2.push(cur2->right);
}
return s1.empty() && s2.empty();
}
};
【101. 对称二叉树】——迭代法 很有意思
-
递归法
1.bool isMirror(TreeNode* leftTree,TreeNode* rightTree),需要知道两棵树是否对称,返回bool2.递归结束条件:root为空,或不为空时左右子树对称则true,左右子树不对称(只有一个不为空,或都不为空但对称位置的值不相等)则false
3.递归逻辑:往下return 左树的左孩子和右树的右孩子 && 右树的左孩子和左树的右孩子是否对称,有点绕?有点绕就对了,我要判断他是不是对称,你告诉我他的孩子都对称他就对称,这,就是递归!
class Solution {
public:
bool isMirror(TreeNode* leftTree,TreeNode* rightTree){
// 首先排除空节点的情况
if(!leftTree && !rightTree) return true;
if(!leftTree || !rightTree) return false;
// 排除了空节点,再排除数值不相同的情况
if(leftTree->val != rightTree->val) return false;
// 此时就是:左右节点都不为空,且数值相同的情况
// 此时才做递归,做下一层的判断
return isMirror(leftTree->left,rightTree->right) &&
isMirror(leftTree->right,rightTree->left);
}
bool isSymmetric(TreeNode* root) {
if(root==nullptr) return true;
return isMirror(root->left,root->right);
}
};
- 迭代法 很有意思,用queue或stack或数组都可以
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == NULL) 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();
if (!leftNode && !rightNode) { // 左节点为空、右节点为空,此时说明是对称的
continue;
}
// 左右一个节点不为空,或者都不为空但数值不相同,返回false
if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
return false;
}
que.push(leftNode->left); // 加入左节点左孩子
que.push(rightNode->right); // 加入右节点右孩子
que.push(leftNode->right); // 加入左节点右孩子
que.push(rightNode->left); // 加入右节点左孩子
}
return true;
}
};
【110. 平衡二叉树】
class Solution {
public:
// 返回以该节点为根节点的二叉树的高度,如果不是二叉搜索树了则返回-1
int getDepth(TreeNode* node) {
if (node == NULL) return 0;
int leftDepth = getDepth(node->left);
if (leftDepth == -1) return -1; // 说明左子树已经不是二叉平衡树
int rightDepth = getDepth(node->right);
if (rightDepth == -1) return -1; // 说明右子树已经不是二叉平衡树
return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);
}
bool isBalanced(TreeNode* root) {
return getDepth(root) == -1 ? false : true;
}
};
【222. 完全二叉树的节点个数】
——套娃
时间复杂度:O(n)
空间复杂度:O(1)
这题很好的解释递归:我想求root的节点数,那么我求出root->left和root->right的节点数再+1就行了,套娃就是递归!
- 版本一
// 版本一
class Solution {
private:
int getNodesNum(TreeNode* cur) {
if (cur == 0) return 0;
int leftNum = getNodesNum(cur->left); // 左
int rightNum = getNodesNum(cur->right); // 右
int treeNum = leftNum + rightNum + 1; // 中
return treeNum;
}
public:
int countNodes(TreeNode* root) {
return getNodesNum(root);
}
};
- 精简
class Solution {
public:
int countNodes(TreeNode* root) {
if(root==nullptr) return 0;
return countNodes(root->left) + countNodes(root->right) +1;
}
};
——利用完全二叉树性质
时间复杂度:O(logn * logn)
空间复杂度:O(logn)
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr) return 0;
TreeNode* left = root->left;
TreeNode* right = root->right;
int leftHeight = 0, rightHeight = 0; // 这里初始为0是有目的的,为了下面求指数方便
while (left) { // 求左子树深度
left = left->left;
leftHeight++;
}
while (right) { // 求右子树深度
right = right->right;
rightHeight++;
}
if (leftHeight == rightHeight) {// (2<<1) 左移一位即00000010----->00000100(相当于2^2),
return (2 << leftHeight) - 1; 所以leftHeight初始为0
}
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
【257. 二叉树的所有路径】
——明显的递归,隐藏着回溯
- 保姆级 注意&
class Solution {
public:
void travel(TreeNode* cur,vector<int>& vec,vector<string>& res){
vec.push_back(cur->val);
if(!cur->left && !cur->right){//叶子节点
string sPath;
for(int i = 0;i<vec.size()-1;i++){
sPath += to_string(vec[i]);
sPath += "->";
}
sPath += to_string(vec[vec.size()-1]);
res.push_back(sPath);
return;
}
if(cur->left){//很有意思,有一个递归,就有一个回溯
travel(cur->left,vec,res);
vec.pop_back();//回溯
}
if(cur->right){
travel(cur->right,vec,res);
vec.pop_back();//回溯
}
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<int> vec;
vector<string> res;
if(root==nullptr) return res;
travel(root,vec,res);
return res;
}
};
- 精简版
class Solution {
public:
void travel(TreeNode* cur,string path,vector<string>& res){
path += to_string(cur->val);
if(!cur->left && !cur->right){
res.push_back(path);
return;
}
if(cur->left){travel(cur->left,path + "->",res);}
if(cur->right){travel(cur->right,path + "->",res);}
}
vector<string> binaryTreePaths(TreeNode* root) {
string path;
vector<string> res;
if(root==nullptr) return res;
travel(root,path,res);
return res;
}
};
【404. 左叶子之和】
- 通常是通过当前节点的左右孩子来判断属性,这题是通过当前节点的父亲节点来判断属性
- 解决一个节点,剩下交给框架,这,就是递归!
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
int res = 0;
if(root == nullptr) return res;
if(root->left && !root->left->left && !root->left->right){res = root->left->val;}
return res + sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right);
}
};
- 当然,迭代法不能忘
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if(root==nullptr) return 0;
stack<TreeNode*> s;
s.push(root);
int res = 0;
while(!s.empty()){
TreeNode* cur = s.top();s.pop();
if(cur->left && !cur->left->left &&!cur->left->right){res += cur->left->val;}
if(cur->left) s.push(cur->left);
if(cur->right) s.push(cur->right);
}
return res;
}
};
【513. 找树左下角的值】
递归有点难理解,主要还是回溯不熟练,相反这题迭代很友好,层序遍历秒刷
【112. 路径总和】
递归函数什么时候需要返回值?
遍历整棵树不用,某一路径则需要,遇到合适的要及时返回(注意返回类型)
- 递归三部曲
1.确定返回类型和参数,此题遍历某一路径,需要返回值
2.递归终止条件:用递减,找到叶子节点时count为0则true,不为0则false
3.单层递归逻辑:traversal是有返回值的,如果他孩子返回true,他就返回true。注意回溯!
class Solution {
private:
bool traversal(TreeNode* cur, int count) {
if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
if (!cur->left && !cur->right) 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;
}
public:
bool hasPathSum(TreeNode* root, int sum) {
if (root == NULL) return false;
return traversal(root, sum - root->val);
}
};
- 迭代
typedef pair<TreeNode*,int> pti;
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if(root == nullptr) return false;
stack<pti> s;
s.push({root,root->val});
while(!s.empty()){
auto [node,sum] = s.top();s.pop();
if(!node->left && !node->right && sum==targetSum) return true;
if(node->left){
s.push({node->left,node->left->val+sum});
}
if(node->right){
s.push({node->right,node->right->val+sum});
}
}
return false;
}
};
【113. 路径总和 II】
class Solution {
private:
vector<vector<int>> result;//写在上面就不用&了
vector<int> path;
// 递归函数不需要返回值,因为我们要遍历整个树
void traversal(TreeNode* cur, int count) {
if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径
result.push_back(path);
return;
}
if (!cur->left && !cur->right) return ; // 遇到叶子节点而没有找到合适的边,直接返回
if (cur->left) { // 左 (空节点不遍历)
path.push_back(cur->left->val);
count -= cur->left->val;
traversal(cur->left, count); // 递归
count += cur->left->val; // 回溯
path.pop_back(); // 回溯
}
if (cur->right) { // 右 (空节点不遍历)
path.push_back(cur->right->val);
count -= cur->right->val;
traversal(cur->right, count); // 递归
count += cur->right->val; // 回溯
path.pop_back(); // 回溯
}
return ;
}
public:
vector<vector<int>> pathSum(TreeNode* root, int sum) {
result.clear();
path.clear();
if (root == NULL) return result;
path.push_back(root->val); // 把根节点放进路径
traversal(root, sum - root->val);
return result;
}
};
【129. 求根节点到叶节点数字之和】
无非就是用一个sum来记录前面的和值,改成pre不行是因为TreeNode没有指数运算
class Solution {
public:
int res = 0;
void travel(TreeNode* root,int sum){
sum = sum*10 + root->val;
if(!root->left && !root->right){
res += sum;
return;
}
if(root->left){travel(root->left,sum);}
if(root->right){travel(root->right,sum);}
}
int sumNumbers(TreeNode* root) {
if(root == nullptr) return 0;
travel(root,0);
return res;
}
};
2、二叉树修改与构造
【226. 翻转二叉树】
- 递归法——注意swap是一整棵树一起交换,不只是root
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
swap(root->left, root->right); // 中
invertTree(root->left); // 左
invertTree(root->right); // 右
return root;
}
};
- 迭代法
dfs(用stack)/前序遍历 先swap左还是右无所谓,只要别把swap放中间变成中序遍历就行
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
stack<TreeNode*> st;
st.push(root);
while(!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
swap(node->left, node->right);
if(node->right) st.push(node->right); // 右
if(node->left) st.push(node->left); // 左
}
return root;
}
};
bfs(用queue)/层序遍历
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) 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) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return root;
}
};
此外这题用中序遍历也很有意思
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
invertTree(root->left); // 左
swap(root->left, root->right); // 中
invertTree(root->left); // 注意 这里依然要遍历左孩子,因为中间节点已经翻转了
return root;
}
};
【106. 从中序与后序遍历序列构造二叉树】
-
第一步:如果数组大小为零的话,说明是空节点了。
-
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
-
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
-
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
-
第五步:切割后序数组,切成后序左数组和后序右数组
-
第六步:递归处理左区间和右区间
class Solution {
private:
TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
if (postorder.size() == 0) return NULL;
// 后序遍历数组最后一个元素,就是当前的中间节点
int rootValue = postorder[postorder.size() - 1];
TreeNode* root = new TreeNode(rootValue);
// 叶子节点
if (postorder.size() == 1) return root;
// 找到中序遍历的切割点
int delimiterIndex;
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
if (inorder[delimiterIndex] == rootValue) break;
}
// 切割中序数组
// 左闭右开区间:[0, delimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
// [delimiterIndex + 1, end)
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end() );
// postorder 舍弃末尾元素
postorder.resize(postorder.size() - 1);
// 切割后序数组
// 依然左闭右开,注意这里使用了左中序数组大小作为切割点
// [0, leftInorder.size)
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
// [leftInorder.size(), end)
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
root->left = traversal(leftInorder, leftPostorder);
root->right = traversal(rightInorder, rightPostorder);
return root;
}
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.size() == 0 || postorder.size() == 0) return NULL;
return traversal(inorder, postorder);
}
};
- 如上的代码性能并不好,因为每层递归定义了新的vector,既耗时又耗空间,优化:
class Solution {
private:
// 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd)
TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) {
if (postorderBegin == postorderEnd) return NULL;
int rootValue = postorder[postorderEnd - 1];
TreeNode* root = new TreeNode(rootValue);
if (postorderEnd - postorderBegin == 1) return root;
int delimiterIndex;
for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
if (inorder[delimiterIndex] == rootValue) break;
}
// 切割中序数组
// 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
int leftInorderBegin = inorderBegin;
int leftInorderEnd = delimiterIndex;
// 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
int rightInorderBegin = delimiterIndex + 1;
int rightInorderEnd = inorderEnd;
// 切割后序数组
// 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd)
int leftPostorderBegin = postorderBegin;
int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin; // 终止位置是 需要加上 中序区间的大小size
// 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd)
int rightPostorderBegin = postorderBegin + (delimiterIndex - inorderBegin);
int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了
root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, postorder, leftPostorderBegin, leftPostorderEnd);
root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd);
return root;
}
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.size() == 0 || postorder.size() == 0) return NULL;
// 左闭右开的原则
return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());
}
};
【105. 从前序与中序遍历序列构造二叉树】
- 因为vector.erase(vec.begin())会报错,所以数组不是很好写,用下标吧,性能还好
class Solution {
private:
TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& preorder, int preorderBegin, int preorderEnd) {
if (preorderBegin == preorderEnd) return NULL;
int rootValue = preorder[preorderBegin]; // 注意用preorderBegin 不要用0
TreeNode* root = new TreeNode(rootValue);
if (preorderEnd - preorderBegin == 1) return root;
int delimiterIndex;
for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
if (inorder[delimiterIndex] == rootValue) break;
}
// 切割中序数组
// 中序左区间,左闭右开[leftInorderBegin, leftInorderEnd)
int leftInorderBegin = inorderBegin;
int leftInorderEnd = delimiterIndex;
// 中序右区间,左闭右开[rightInorderBegin, rightInorderEnd)
int rightInorderBegin = delimiterIndex + 1;
int rightInorderEnd = inorderEnd;
// 切割前序数组
// 前序左区间,左闭右开[leftPreorderBegin, leftPreorderEnd)
int leftPreorderBegin = preorderBegin + 1;
int leftPreorderEnd = preorderBegin + 1 + delimiterIndex - inorderBegin; // 终止位置是起始位置加上中序左区间的大小size
// 前序右区间, 左闭右开[rightPreorderBegin, rightPreorderEnd)
int rightPreorderBegin = preorderBegin + 1 + (delimiterIndex - inorderBegin);
int rightPreorderEnd = preorderEnd;
root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, preorder, leftPreorderBegin, leftPreorderEnd);
root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, preorder, rightPreorderBegin, rightPreorderEnd);
return root;
}
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (inorder.size() == 0 || preorder.size() == 0) return NULL;
// 参数坚持左闭右开的原则
return traversal(inorder, 0, inorder.size(), preorder, 0, preorder.size());
}
};
【654. 最大二叉树】
- 和前两题一个思路,甚至更简单点
class Solution {
public:
TreeNode* travel(vector<int>& nums,int numsBegin,int numsEnd){
if(numsEnd-numsBegin<=0) return nullptr;
int MaxIndex = numsBegin;
for(int i = numsBegin+1;i<numsEnd;i++){
if(nums[i]>nums[MaxIndex]) MaxIndex = i;
}
int rootVal = nums[MaxIndex];
TreeNode* root = new TreeNode(rootVal);
int leftBegin = numsBegin;
int leftEnd = MaxIndex;
int rightBegin = MaxIndex+1;
int rightEnd = numsEnd;
root->left = travel(nums,leftBegin,leftEnd);
root->right = travel(nums,rightBegin,rightEnd);
return root;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
if(nums.size()==0) return nullptr;
return travel(nums,0,nums.size());
}
};
【617. 合并二叉树】
- 利用已有的树,不用创建新树搞晕自己
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(!root1) return root2;
if(!root2) return root1;
root1->val += root2->val;
root1->left = mergeTrees(root1->left,root2->left);
root1->right = mergeTrees(root1->right,root2->right);
return root1;
}
};
3、BST——中序遍历,大有可为
——————pre指针,常用技巧
——————莫忘更新,pre=root!
【98. 验证二叉搜索树】
- 递归(中序遍历得顺序数组+检查)
class Solution {
public:
vector<int> vec;
//中序遍历得顺序数组
void travel(TreeNode* root){
if(root==nullptr) return;
travel(root->left);
vec.push_back(root->val);
travel(root->right);
}
//检查是否顺序得结果
bool isValidBST(TreeNode* root) {
travel(root);
for(int i = 1;i<vec.size();i++){
if(vec[i]<=vec[i-1]) return false;
}
return true;
}
};
- 递归优化
class Solution {
public:
TreeNode* pre = nullptr;//虚拟节点 用来记录前一个节点
bool isValidBST(TreeNode* root) {
if(root == nullptr) return true;
bool left = isValidBST(root->left);
if(pre!=nullptr && pre->val >= root->val) return false;
pre = root;//更新节点
bool right = isValidBST(root->right);
return left && right;
}
};
【700.二叉搜索树中的搜索】
- 递归
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if(root==nullptr || root->val == val) return root;
if(root->val < val) return searchBST(root->right,val);
if(root->val > val) return searchBST(root->left,val);
return nullptr;
}
};
- 迭代(痛哭流涕的迭代法——得幸于其有序)
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
while(root){
if(root->val > val) root = root->left;
else if(root->val < val) root = root->right;
else return root;
}
return nullptr;
}
};
【701.二叉搜索树中的插入操作】
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root==nullptr) return new TreeNode(val);
if(root->val > val) root->left = insertIntoBST(root->left,val);
if(root->val < val) root->right = insertIntoBST(root->right,val);
return root;
}
};
【450.删除二叉搜索树中的节点】
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
if (root->val == key) {
// 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
// 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
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; // 把要删除的节点(root)左子树放在cur的左孩子的位置
TreeNode* tmp = root; // 把root节点保存一下,下面来删除
root = root->right; // 返回旧root的右孩子作为新root
delete tmp; // 释放节点内存(这里不写也可以,但C++最好手动释放一下吧)
return root;
}
}
if (root->val > key) root->left = deleteNode(root->left, key);
if (root->val < key) root->right = deleteNode(root->right, key);
return root;
}
};
【530. 二叉搜索树的最小绝对差】
- 递归
把二叉搜索树转换成有序数组,然后遍历一遍数组,就统计出来最小差值了
class Solution {
private:
vector<int> vec;
void traversal(TreeNode* root) {
if (root == NULL) return;
traversal(root->left);
vec.push_back(root->val); // 将二叉搜索树转换为有序数组
traversal(root->right);
}
public:
int getMinimumDifference(TreeNode* root) {
vec.clear();
traversal(root);
if (vec.size() < 2) return 0;
int result = INT_MAX;
for (int i = 1; i < vec.size(); i++) { // 统计有序数组的最小差值
result = min(result, vec[i] - vec[i-1]);
}
return result;
}
};
- 递归优化,在中序遍历时加入pre
class Solution {
private:
int result = INT_MAX;
TreeNode* pre;
void traversal(TreeNode* cur) {
if (cur == NULL) return;
traversal(cur->left); // 左
if (pre != NULL){ // 中
result = min(result, cur->val - pre->val);
}
pre = cur; // 记录前一个
traversal(cur->right); // 右
}
public:
int getMinimumDifference(TreeNode* root) {
traversal(root);
return result;
}
};
- 迭代
class Solution {
public:
int getMinimumDifference(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* pre = NULL;
int result = INT_MAX;
while (cur != NULL || !st.empty()) {
if (cur != NULL) { // 指针来访问节点,访问到最底层
st.push(cur); // 将访问的节点放进栈
cur = cur->left; // 左
} else {
cur = st.top();
st.pop();
if (pre != NULL) { // 中
result = min(result, cur->val - pre->val);
}
pre = cur;
cur = cur->right; // 右
}
}
return result;
}
};
【501. 二叉搜索树中的众数】
- 最直观的方法一定是把这个树都遍历了,用 map 统计频率,用 vector 排个序,最后return前面高频的元素。
class Solution {
public:
unordered_map<int,int> mp;
void travel(TreeNode* root){
if(root==nullptr) return;
travel(root->left);
mp[root->val]++;
travel(root->right);
}
bool static cmp(const pair<int,int> &a,const pair<int,int> &b) {return a.second > b.second;}
vector<int> findMode(TreeNode* root) {
vector<int> res;
if(root==nullptr) return res;
res.clear();
travel(root);
vector<pair<int,int>> vec(mp.begin(),mp.end());
sort(vec.begin(),vec.end(),cmp);
res.push_back(vec[0].first);
for(int i = 1;i<vec.size();i++){
if(vec[i].second==vec[0].second) res.push_back(vec[i].first);
}
return res;
}
};
——和前一个节点比较,体会这种思想
class Solution {
public:
TreeNode* pre = nullptr;
int count = 0;
int maxCount = 0;
vector<int> res;
void travel(TreeNode* root){
if(root==nullptr) return;
travel(root->left);
if(pre==nullptr) count = 1;
else if(pre->val == root->val) count++;
else count = 1;
pre = root;//总是容易忘记这一步
if(count == maxCount) res.push_back(root->val);
if(count > maxCount){
maxCount = count;
res.clear();
res.push_back(root->val);
}
travel(root->right);
}
vector<int> findMode(TreeNode* root) {
if(root==nullptr) return res;
travel(root);
return res;
}
};
【538. 把二叉搜索树转换为累加树】
- pre指针,常用技巧
- 若是数组则从后往前累加,所以树累加的顺序是右中左,反中序遍历这个二叉树,然后累加就可以了。
class Solution {
public:
TreeNode* pre = nullptr;
void travel(TreeNode* root){
if(root == nullptr) return;
travel(root->right);//右
if(pre) root->val += pre->val;//中
pre = root;
travel(root->left);//左
}
TreeNode* convertBST(TreeNode* root) {
if(root == nullptr) return root;
travel(root);
return root;
}
};
【108. 将有序数组转换为二叉搜索树】
- 左闭右开,yyds
class Solution {
public:
TreeNode* travel(vector<int>& nums,int numsBegin,int numsEnd){
if(numsBegin>=numsEnd) return nullptr;
int mid = (numsEnd - numsBegin)/2+numsBegin;
TreeNode* root = new TreeNode(nums[mid]);
root->left = travel(nums,numsBegin,mid);
root->right = travel(nums,mid+1,numsEnd);
return root;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return travel(nums,0,nums.size());
}
};
4、公共祖先问题
【236. 二叉树的最近公共祖先】
- 思路:自底向上——回溯——后序遍历(后序遍历就是天然的回溯过程,最先处理的一定是叶子节点)
——如何判断一个节点是节点q和节点p的公共祖先——若pq分别出现在root左右子树,root就是公共祖先
递归三部曲:
-
确定递归函数返回值以及参数
需要递归函数返回值,来告诉我们是否找到节点q或者p,那么返回值为bool类型就可以了。
但我们还要返回最近公共节点,可以利用上题目中返回值是TreeNode * ,那么如果遇到p或者q,就把q或者p返回,返回值不为空,就说明找到了q或者p。
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
- 确定终止条件
如果找到了 节点p或者q,或者遇到空节点,就返回。
if(root==q || root==p ||root==NULL) return root;
-
确定单层递归逻辑
值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。
-
但事实上还要遍历根节点右子树(即使此时已经找到了目标节点了)
因为在如上代码的后序遍历中,如果想利用left和right做逻辑处理, 不能立刻返回,而是要等left与right逻辑处理完之后才能返回。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==q || root==p ||root==NULL) return root;
TreeNode* left = lowestCommonAncestor(root->left,p,q);
TreeNode* right = lowestCommonAncestor(root->right,p,q);
if(left && right) return root;
if(!left) return right;
else return left;
}
};
【235. BST的最近公共祖先】
因为是BST,只要从上到下遍历的时候,cur节点是数值在[p, q]区间中则说明该节点cur就是最近公共祖先了
- 递归
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root->val > p->val && root->val > q->val) {
return lowestCommonAncestor(root->left, p, q);
} else if (root->val < p->val && root->val < q->val) {
return lowestCommonAncestor(root->right, p, q);
} else return root;
}
};
- 迭代
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
while(root) {
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;
}
};