二叉树
98.验证二叉树
可以用递归,或者迭代中序遍历。通过预先定义一个pre节点,使其充当前一个节点,让其与当前cur节点进行比较,pre节点值大于等于当前cur节点的val,即return false。
//迭代法
bool isValidBST(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur=root;
while(cur!=NULL || !st.empty()){
if(cur != NULL){
st.push(cur);
cur=cur->left;
}
else{
cur=st.top();
st.pop();
if(pre!=NULL && pre->val>=cur->val) return false;
pre=cur;
cur=cur->right;
}
}
return true;
}
//递归法
bool isValidBST(TreeNode* root) {
if(root == NULL) return true;
bool left=isValidBST(root->left);
if(pre!=NULL && pre->val >= root->val) return false;
pre=root;
bool right=isValidBST(root->right);
return left&&right;
}
530.二叉搜索树的最小绝对差
本人做法:
对二叉搜索树进行中序遍历,结果输出到vector中,进行for循环,求相邻元素差的最小值即为最小绝对差。与98题一样,同样可以用递归和栈中序遍历解题,设置pre,求cur-pre的最小差值。
501.二叉搜索树中的众数*(Binary Search Tree)
递归代码如下:
class Solution {
public:
int count=0;
int maxcount=0;
vector<int> res;
TreeNode* pre=NULL;
void traversal(TreeNode* root){
if(root == NULL) return;
traversal(root->left);
if(pre == NULL) 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);
}
traversal(root->right);
return;
}
vector<int> findMode(TreeNode* root) {
count=0;
maxcount=0;
res.clear();
pre=NULL;
traversal(root);
return res;
}
};
若是普通二叉树,可以通过三种遍历(递归即可),并由map统计val出现的次数,并设置一个vector,将pair<int,int>(值,出现的次数)添加到vector中,进行排序。求出出现频率最高的元素。
若是二叉搜索树,则可以通过中序遍历进行求解。通过设置出现频率和最大出现频率,当当前频率大于最大频率时,更新最大频率,完成求解。
236.二叉树的最近公共祖先*
本题用到了递归回溯思想,迭代法并不好模拟这种回溯思想,完成流程图如图所示:
class Solution {
public:
//应后序遍历
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==NULL || root==p || root==q) return root;
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
if(left!=NULL && right!=NULL) return root;
else if(left==NULL && right!=NULL) return right;
else if(left!=NULL && right==NULL) return left;
else{
return NULL;
}
}
};
那么我给大家归纳如下三点:
-
求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
-
在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
-
要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。
可以说这里每一步,都是有难度的,都需要对二叉树,递归和回溯有一定的理解。
小结
现在已经讲过了几种二叉树了,二叉树,平衡二叉树,完全二叉树,二叉搜索树,后面还会有平衡二叉搜索树。 那么一些同学难免会有混乱了,我针对如下三个问题,帮大家在捋顺一遍:
-
平衡二叉搜索树是不是二叉搜索树和平衡二叉树的结合?
是的,是二叉搜索树和平衡二叉树的结合。 -
平衡二叉树与完全二叉树的区别在于底层节点的位置?
是的,完全二叉树底层必须是从左到右连续的,且次底层是满的。 -
堆是完全二叉树和排序的结合,而不是平衡二叉搜索树?
堆是一棵完全二叉树,同时保证父子节点的顺序关系(有序)。但完全二叉树一定是平衡二叉树,堆的排序是父节点大于子节点,而搜索树是父节点大于左孩子,小于右孩子,所以堆不是平衡二叉搜索树。
关于几种树的定义:
二叉树节点的深度和高度:
深度: 指从根节点到该节点的最长简单路径边的条数。
高度: 指从该节点到叶子节点的最长简单路径边的条数。
完全二叉树: 在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
平衡二叉树: 平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。
二叉搜索树是一个有序树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉搜索树。(有关二叉搜索树的题要想到中序遍历)
701.二叉搜索树中的插入操作(自解)
自解迭代代码如下:
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
TreeNode* newnode=new TreeNode(val);
if(root == NULL){
return newnode;
}
TreeNode* cur=root;
bool flag=true;
while(root != NULL){
if(val<root->val){
if(root->left==NULL){
flag=true;
break;
}
root=root->left;
}
else if(val > root->val){
if(root->right==NULL){
flag=false;
break;
}
root=root->right;
}
}
if(flag==true){
root->left=newnode;
}
else{
root->right=newnode;
}
return cur;
}
};
迭代代码:
通过递归函数的返回值,完成了新加入节点的父子关系赋值操作。下一层将新加入的节点返回,本层用root->left或者root->right将其接住。
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == NULL) {
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;
}
};
题外话:
如果递归函数有返回值,如何区分搜索一条边还是搜索整个树?
搜索一条边的写法:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
搜索整个树的写法:
left = 递归函数(root->left);
right = 递归函数(root->right);
left与right的逻辑处理;
450.删除二叉搜索树中的节点**
需要考虑到五种情况:
没找到需要删除的节点:
- 没找到需要删除的节点,遍历到空节点直接返回。
找到了需要删除的节点:
- 左右孩子都为空(叶子节点),直接删除,return NULL。
- 左孩子为空,右孩子不为空,直接删除节点,右孩子补位。
- 右孩子为空,左孩子不为空,直接删除节点,左孩子补位。
- 左右孩子都不为空,找到右孩子的左孩子的最左边子节点,将待删除节点的左孩子,充当其左孩子。
代码如下所示:
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if(root == NULL) return NULL;
if(root->val == key){
if(root->left==NULL && root->right==NULL){
delete root;
return nullptr;
}
else if(root->left==NULL && root->right!=NULL){
TreeNode* node=root->right;
delete root;
return node;
}
else if(root->right == NULL){
TreeNode* node=root->left;
delete root;
return node;
}
else{
TreeNode* cur=root->right;
while(cur->left!=NULL){
cur=cur->left;
}
cur->left=root->left;
TreeNode* tmp=root;
root=root->right;
delete tmp;
return root;
}
}
//这里相当于把新的节点返回给上一层,上一层就要用 root->left
//或者 root->right接住
if(root->val < key){
root->right=deleteNode(root->right,key);
}
if(root->val > key){
root->left=deleteNode(root->left,key);
}
return root;
}
};
下面代码相当于把新的节点返回给上一层,上一层就要用 root->left 或者 root->right接住。
if(root->val < key){
root->right=deleteNode(root->right,key);
}
if(root->val > key){
root->left=deleteNode(root->left,key);
}
669.修剪二叉搜索树
递归代码如下:
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if(root == NULL) return root;
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.将有序数组转换为二叉搜索树
递归代码如下:
class Solution {
private:
TreeNode* traversal(vector<int>& nums, int left, int right){
//确定终止条件
if(left > right) return nullptr;
//确定单层递归逻辑
int mid=left+(right-left)/2;
TreeNode* node=new TreeNode(nums[mid]);
node->left=traversal(nums,left,mid-1);
node->right=traversal(nums,mid+1,right);
return node;
}
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
TreeNode* root=traversal(nums, 0, nums.size()-1);
return root;
}
};
代码中用int mid=left+(right-left)/2代替了int mid=(left+right)/2,是为了防止越界问题的发生,因为right和left有可能是int最大值。当nums的size是偶数时,mid取左边。编程过程中,始终坚持左闭右闭原则。该题的思想与654.最大二叉树,450.删除二叉搜索树中的节点,701.二叉搜索树中的插入操作,比较相似。(牢记递归过程中,用本层的root->left或者root->right去接住上一层返回来的节点,同时避免在每层递归中创建vector浪费内存,应该利用数组下标进行编程)
538.把二叉搜索树转换为累加树*
刚开始看到这个题目的描述可能会有点蒙,看懂之后就会觉得豁然开朗。本题可通过逆中序遍历的方法,并设置一个全局的pre记录上一个节点的val,即可完成求解。在leetcode501.二叉搜索树中的众数、530.二叉搜索树的最小绝对差中,也采用了设置全局pre记录上一个节点的方法。
class Solution {
private:
int pre;
//逆中序遍历,右中左
void traversl(TreeNode* root){
if(root == NULL) return;
traversl(root->right);
root->val+=pre;
pre=root->val;
traversl(root->left);
return;
}
public:
TreeNode* convertBST(TreeNode* root) {
pre=0;
traversl(root);
return root;
}
};