文章目录
知识回顾
二叉排序树(二叉搜索树)的基础知识在之前的文章中有详细介绍,包括概念、插入、删除等基本操作,并进行了代码演示。
AVL树作为一种特殊的二叉排序树,对树高做了限制,在之前的文章中也有介绍,包括基本概念,性质,插入与删除过程中出现失衡后如何调整。并进行了代码演示。
例题讲解
1. 面试题17.12: BiNode
题目链接
题目解析:利用结构化思维,将二叉搜索树看成一个有序序列,中序遍历时记录并维护前一个遍历过的节点,让前一个节点的右子树指向当前节点,再将当前节点的左子树置空即可。
/**
* 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 *pre, *head;
void inorder(TreeNode *root) {
if (root == nullptr) return;
inorder(root->left);
if (pre == nullptr) {
head = root;
}else {
pre->right = root;
}
root->left = nullptr;
pre = root;
inorder(root->right);
return ;
}
TreeNode* convertBiNode(TreeNode* root) {
pre = head = nullptr;
inorder(root);
return head;
}
};
注意:本题不能简单的将左右子树分别转换为链表,再针对根节点将左右子树原地调整,因为试过如下代码:
class Solution {
public:
TreeNode* convertBiNode(TreeNode* root) {
if (root == nullptr) return root;
TreeNode *left = convertBiNode(root->left);
TreeNode *right = convertBiNode(root->right);
if (left == nullptr) return root;
TreeNode *node = left;
root->left = nullptr;
while (node->right) {
node = node->right;
}
node->right = root;
return left;
}
};
这样右子树的左子树会都丢失,只剩下右子树的右子树,而且答案也是错误的。为避免递归遍历的各种指针问题,中序遍历是最稳妥的。
2. 剑指Offer 33: 二叉搜索树的后续遍历序列
题目链接
题目解析:同样使用结构化思维,将后续序列数组 看成一棵二叉树,对这棵树进行中序遍历。遍历的过程中维护和记录前一个节点,看当前节点是否大于前一个节点,出现异常情况则直接返回false。
class Solution {
public:
int pre = -1; //遍历的前一个节点,用下标表示
bool inorder(vector<int> &nums, int l, int r) {
if (l > r) return true;
int ind = l;
while (nums[ind] < nums[r]) ind++; //找到右子树位置,这里不需要二分查找
if (!inorder(nums, l, ind - 1)) return false;
if (pre != -1 && nums[r] < nums[pre]) return false;
pre = r;
if (!inorder(nums, ind, r - 1)) return false;
return true;
}
bool verifyPostorder(vector<int>& postorder) {
return inorder(postorder, 0, postorder.size() - 1);
}
};
总结:开始想的是是否能转化为二叉搜索树,但是考虑到二叉搜索树常用的结构化思维,可以直接在数组中进行中序遍历。
代码小技巧:
- 中序遍历inorder函数直接返回值,这样不用维护是否遍历成功的全局变量,左子树或右子树遍历失败时也可以直接返回。
- 前一个节点用数组下标表示,这样初始化为-1,就不需要使用一个额外变量来判断当前节点是否是遍历的第一个节点。
- 寻找第一个大于根节点nums[r]节点的下标,这里用了直接依次比较法,而没有用二分法,因为从全局来看,这样依次比较也相当于是最终最多遍历了一遍数组。试过用二分法查找和这样依次比较查找的差异,二分法查找结果会很慢。原因应该是因为有一些不满足二叉搜索树的序列,在很早的位置就到达了搜索重点,而依次比较法会差的很快。
3. Leetcode 1008: 前序遍历构造二叉搜索树
题目链接
题目解析:和前一道题目类似,首先对于第一个值构造一棵单节点二叉树,然后往后依次找到右子树节点的位置(第一个大于根节点的值),然后对于左子树和右子树分别构造一棵二叉树即可。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode *dfs(vector<int> &nums, int l, int r) {
if (l > r) return nullptr;
TreeNode *root = new TreeNode(nums[l]);
int ind = l + 1;
//找到右子树的位置,这里和上一道题一样也不需要二分搜索
while (ind < nums.size() && nums[ind] < nums[l]) ind++;
root->left = dfs(nums, l + 1, ind - 1);
root->right = dfs(nums, ind, r);
return root;
}
TreeNode* bstFromPreorder(vector<int>& preorder) {
if(preorder.size() == 0) return nullptr;
TreeNode *root = dfs(preorder, 0, preorder.size() - 1);
return root;
}
};
代码上注意对于ind的范围判断。
4. 面试题04.09: 二叉搜索树序列
题目链接
题目解析:首先要求的序列应该满足一个基本性质,即任意一个节点必须出现在其左右子树所有节点的前面。
当获得了左子树和右子树的分别一个序列后,只需要递归的遍历两个序列,从其中任意一个序列中按从前到后的顺序取出一个数,然后继续遍历后面剩下的数字。
/**
* 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:
void mergeSequences(vector<int> &l, int lind,
vector<int> &r, int rind, vector<int> &buff,
vector<vector<int>> &ret) {
/******************
合并左子树的一个合法序列和右子树的一个合法序列
l: 左子树的一个合法序列
lind: 当前从l中按顺序取了几个数
r: 右子树的一个合法序列
rind: 当前从r中按顺序取了几个数
buff: 缓冲数组,存储每次临时取数的结果,当l和r都取完时,将buff加到结果数组中
ret: 结果数组,当l和r都取完时更新
*****************/
if (lind == l.size() && rind == r.size()) {
ret.push_back(buff);
return;
}
if (lind < l.size()) {
buff.push_back(l[lind]);
mergeSequences(l, lind + 1, r, rind, buff, ret);
buff.pop_back();
}
if (rind < r.size()) {
buff.push_back(r[rind]);
mergeSequences(l, lind, r, rind + 1, buff, ret);
buff.pop_back();
}
return;
}
vector<vector<int>> BSTSequences(TreeNode* root) {
vector<vector<int>> ret;
if (root == nullptr) {
ret.push_back(vector<int> ());
return ret;
}
//找到左右子树的合法序列
vector<vector<int>> l_arr = BSTSequences(root->left);
vector<vector<int>> r_arr = BSTSequences(root->right);
for (auto l : l_arr) {
for (auto r : r_arr) {
vector<int> buff;
buff.push_back(root->val);
//合并左右子树的分别序列
//参数含义见函数定义部分
mergeSequences(l, 0, r, 0, buff, ret);
}
}
return ret;
}
};
总结:首先把握好一条原则:根节点必须出现左所以子节点的前面。然后取左子树和右子树的合法序列用递归,合并左右子树合法序列仍然用递归回溯来实现。