654. 最大二叉树
本题已经给出了构造的要求,首先找到最大值,接着根据最大值分割数组,再递归构建左右子树。
和之前的根据后序和中序数组构造二叉树相似,下面给出代码:
class Solution {
public:
int getindedx(vector<int> vec){
int val = INT_MIN;
int ans;
for(int i = 0; i < vec.size(); i++){
if(vec[i] > val){
ans = i;
val = vec[i];
}
}
return ans;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
if(nums.size() == 0) return NULL;
int rootindex = getindedx(nums);
TreeNode* root = new TreeNode(nums[rootindex]);
if(nums.size() == 1) return root;
vector<int> leftvec(nums.begin() , nums.begin() + rootindex);
vector<int> rightvec(nums.begin() + rootindex + 1, nums.end());
root->left = constructMaximumBinaryTree(leftvec);
root->right = constructMaximumBinaryTree(rightvec);
return root;
}
};
这里我自己定义了一个寻找最大值的函数,其实没有必要,并且在性能和代码上还有很大的优化空间。下面是使用数组下标进行递归的优化代码:
class Solution {
public:
TreeNode* traversal(vector<int>& nums, int left, int right){
if(left >= right) return NULL;
int maxindex = left;
for(int i = left + 1; i < right; i++){
if(nums[maxindex] < nums[i]) maxindex = i;
}
TreeNode* root = new TreeNode(nums[maxindex]);
root->left = traversal(nums, left, maxindex);
root->right = traversal(nums, maxindex + 1, right);
return root;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return traversal(nums, 0, nums.size());
}
};
这样节省了额外数组的内存开销,但是需要注意寻找最大值下标的时候的循环范围是left + 1
到right
。
617. 合并二叉树
本题主要是同时对两棵二叉树进行处理,实际上与处理一颗二叉树差不多,因为遍历两棵树的时候节点处理的顺序是相同的。
使用递归来写,本题使用前中后序遍历都可以,下面是递归三部曲:
- 输入输出:不需要额外再写一个递归函数,直接使用题目给的即可:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2)
。 - 终止条件:这里的终止条件不能写两者都为空,这样在本题中没有意义,而是当两者其中一个为空时的处理
- 单层递归逻辑:这里使用前序,构造好节点后再递归构造左右子树
我自己写的是创建一个新的树来输出结果:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1 == NULL || root2 == NULL){
if(root1 == NULL) return root2;
else if(root2 == NULL) return root1;
else return NULL;
}
int val1 = 0;
int val2 = 0;
if(root1) val1 = root1->val;
if(root2) val2 = root2->val;
TreeNode* root = new TreeNode(val1 + val2);
root->left = mergeTrees(root1->left , root2->left);
root->right = mergeTrees(root1->right , root2->right);
//if(root1->left || root2->left) root->left = mergeTrees(root1->left , root2->left);
//if(root1->right || root2->right) root->right = mergeTrees(root1->right , root2->right);
return root;
}
这里说一下我对终止条件的判断,以及对空节点的处理的理解:
- 如果在终止条件里已经对空节点处理了,那么在递归左右孩子的时候可以不用加上
if(root->left)
这种代码; - 如果终止条件中需要对符合条件的节点进行处理的话,一般情况下这个节点不会为空节点,所以后面对左右孩子的递归不需要加上
if(root->left)
这种代码; - 当需要使用节点内的value时,需要注意空节点的问题,如果有以上两种情况的话实际上可以忽略,因为已经提前确保当前遍历节点不是空节点了。
- 有时候也需要根据递归函数的返回值类型来决定
只是一些自己刷题的感想,也不一定对。
上述代码也可以进一步简化,比如终止条件那里,不需要对左右节点都为NULL
的情况返回false
,因为如果root1为NULL返回root2,root2为NULL返回root1已经包含了两者都为空的情况。另外可以直接对其中一个树直接操作,不用去额外构造一棵树。
代码如下:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1 == NULL) return root2;
if(root2 == NULL) return root1;
root1->val += root2->val;
root1->left = mergeTrees(root1->left, root2->left);
root1->right = mergeTrees(root1->right, root2->right);
return root1;
}
这样就看起来很简洁了。
700. 二叉搜索树中的搜索
本题使用迭代法来做甚至不需要使用队列或者栈来储存节点,因为二叉搜索树的特性,代码也非常简洁,也就不详细说了,代码如下:
TreeNode* searchBST(TreeNode* root, int val) {
while(root){
if(val < root->val) root = root->left;
else if(val > root->val) root = root->right;
else return root;;
}
return NULL;
}
下面详细说一下递归法。
本题递归部分顺序,因为BST本身就是有序的,所以不需要确定前中后序。这里顺便说一下一个特性,在下一题中非常重要:BST的中序遍历是一个递增的数组。
递归三部曲:
- 输入输出:使用函数本身来递归,
TreeNode* searchBST(TreeNode* root, int val)
; - 终止条件:先补充一个概念:空节点是一颗BST也是一颗完全二叉树、满二叉树;所以当节点为空时返回,当节点的值等于target时返回;
- 单层递归逻辑:由于BST的特性,当
root->val
大于target时,说明需要找的节点一定在root节点的左边,反之则在右边,这样就确定了左右递归的逻辑
递归代码如下:
TreeNode* searchBST(TreeNode* root, int val) {
if(root == NULL || root->val == val) return root;
TreeNode* ans = NULL;
if(val < root->val) ans = searchBST(root->left, val);
if(val > root->val) ans = searchBST(root->right, val);
return ans;
}
这里需要新定义一个TreeNode* ans
来接收左右孩子遍历的结果,否则在遍历完左或者右孩子之后没有返回值。
98. 验证二叉搜索树
本题解题的核心思想就是上面提到的BST的中序遍历是递增的,所以思路是在遍历的时候将当前节点的值与前一个结点进行对比,如果大于则符合BST,如果小于则直接返回false。
最简单的写法就是遍历整颗二叉树,然后将结果输出到数组中,最后判断是否是BST,代码如下:
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:
bool isValidBST(TreeNode* root) {
vec.clear(); // 不加这句在leetcode上也可以过,但最好加上
traversal(root);
for (int i = 1; i < vec.size(); i++) {
// 注意要小于等于,搜索树里不能有相同元素
if (vec[i] <= vec[i - 1]) return false;
}
return true;
}
};
本题也可以使用双指针,在一个函数里直接完成解题步骤。因为BST的特性,所以按照中序遍历的递归来写递归函数。
递归三部曲:
- 输入输出:
bool isValidBST(TreeNode* root)
,直接使用主函数,输入自然是结点,输出是bool
; - 终止条件:前面也说了,空结点也是BST,所以终止条件为
if(root == NULL)
; - 单层递归逻辑:使用一个
pre
指针保存前一个结点,然后进行比较,按照中序的顺序写递归函数体
下面是具体代码:
TreeNode* pre = NULL;
bool isValidBST(TreeNode* root) {
if(root == NULL) return true;
bool left = isValidBST(root->left);
if(pre && pre->val >= root->val) return false;
pre = root;
bool right = isValidBST(root->right);
return left && right;
}
总结
一个是返回值和返回类型的问题:最后一题是需要处理递归的结果的,所以需要返回值,并且同时需要两个遍历来接收左右孩子遍历的结果返回上一层;700. 二叉搜索树中的搜索中需要定义一个结点来存放返回值,如果在if语句中直接返回是不符合语法的。
一个是终止条件和空结点的问题:一般情况来说:如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整。
有点累,但还是要咬牙坚持,调整一下状态继续干二叉树!