侧边栏壁纸
博主头像
thinkTV博主等级

喜爱动漫的二刺螈一枚,摩托车云爱好者(快要有车了)。 懂一点技术的在读生物医学工程研究生( •̀ ω •́ )✧,多多指教。

  • 累计撰写 127 篇文章
  • 累计创建 17 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

代码随想录算法训练营第二十天 | 最大二叉树;合并二叉树;二叉搜索树中的搜索;验证二叉搜索树

thinkTV
2023-05-08 / 0 评论 / 0 点赞 / 168 阅读 / 2,975 字 / 正在检测是否收录...

1. 最大二叉树

代码随想录: 原文

力扣题目: 654.最大二叉树

图片-1683508607057

1.1 思路

构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树

1. 确定递归函数的参数和返回值

  • 参数传入的是存放元素的数组
  • 返回该数组构造的二叉树的头结点,返回类型是指向节点的指针
TreeNode* constructMaximumBinaryTree(vector<int>& nums)

2. 确定终止条件

  • 输入的数组大小一定是大于等于1的
  • 当递归遍历的时候,如果传入的数组大小为1,说明遍历到了叶子节点
  • 应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点
  • 表示一个数组大小是1的时候,构造了一个新的节点,并返回
TreeNode* node = new TreeNode(0);
if (nums.size() == 1) {
    node->val = nums[0];
    return node;
}

3. 确定单层递归的逻辑

  • 先要找到数组中最大的值和对应的下标,
  • 最大的值构造根节点,下标用来下一步分割数组
int maxValue = 0;
int maxValueIndex = 0;
for (int i = 0; i < nums.size(); i++) {
    if (nums[i] > maxValue) {
        maxValue = nums[i];
        maxValueIndex = i;
    }
}
TreeNode* node = new TreeNode(0);
node->val = maxValue;

  • 最大值所在的下标左区间 构造左子树
  • 判断maxValueIndex > 0,保证左区间至少有一个数值
if (maxValueIndex > 0) {
    vector<int> newVec(nums.begin(), nums.begin() + maxValueIndex);
    node->left = constructMaximumBinaryTree(newVec);
}

  • 最大值所在的下标右区间 构造右子树
  • 判断maxValueIndex < (nums.size() - 1),确保右区间至少有一个数值
if (maxValueIndex < (nums.size() - 1)) {
    vector<int> newVec(nums.begin() + maxValueIndex + 1, nums.end());
    node->right = constructMaximumBinaryTree(newVec);
}

1.2 代码实现

class Solution {
public:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        TreeNode* node = new TreeNode(0);
        if (nums.size() == 1) {
            node->val = nums[0];
            return node;
        }
        // 找到数组中最大的值和对应的下标
        int maxValue = 0;
        int maxValueIndex = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] > maxValue) {
                maxValue = nums[i];
                maxValueIndex = i;
            }
        }
        node->val = maxValue;
        // 最大值所在的下标左区间 构造左子树
        if (maxValueIndex > 0) {
            vector<int> newVec(nums.begin(), nums.begin() + maxValueIndex);
            node->left = constructMaximumBinaryTree(newVec);
        }
        // 最大值所在的下标右区间 构造右子树
        if (maxValueIndex < (nums.size() - 1)) {
            vector<int> newVec(nums.begin() + maxValueIndex + 1, nums.end());
            node->right = constructMaximumBinaryTree(newVec);
        }
        return node;
    }
};

  • 每次还要切割的时候每次都要定义新的vector
  • 代码冗余,效率不高
  • 优化: 每次分隔不用定义新的数组,而是通过下标索引直接在原数组上操作
class Solution {
private:
    // 在左闭右开区间[left, right),构造二叉树
    TreeNode* traversal(vector<int>& nums, int left, int right) {
        if (left >= right) return nullptr;

        // 分割点下标:maxValueIndex
        int maxValueIndex = left;
        for (int i = left + 1; i < right; ++i) {
            if (nums[i] > nums[maxValueIndex]) maxValueIndex = i;
        }

        TreeNode* root = new TreeNode(nums[maxValueIndex]);

        // 左闭右开:[left, maxValueIndex)
        root->left = traversal(nums, left, maxValueIndex);

        // 左闭右开:[maxValueIndex + 1, right)
        root->right = traversal(nums, maxValueIndex + 1, right);

        return root;
    }
public:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return traversal(nums, 0, nums.size());
    }
};

  • 第二版代码是允许空节点进入递归,所以没有加if判断,当然终止条件也要有相应的改变。
  • 第一版终止条件,是遇到叶子节点就终止,因为空节点不会进入递归。
  • 第二版相应的终止条件,是遇到空节点,也就是数组区间为0,就终止了。

2. 合并二叉树

代码随想录: 原文

力扣题目: 617.合并二叉树

图片-1683510885778

2.1 思路

2.1.1 递归法

本题使用哪种遍历都可以

1. 确定递归函数的参数和返回值

  • 传入两个二叉树的根节点
  • 返回值就是合并之后二叉树的根节点
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {

2. 确定终止条件

  • 传入了两个树,有两个树遍历的节点t1 和 t2
  • 如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)
  • 如果t2 == NULL,那么两个数合并就是t1
if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1

3. 确定单层递归的逻辑

  • 重复利用t1
  • t1就是合并之后树的根节点(就是修改了原来树的结构)
t1->left = mergeTrees(t1->left, t2->left);
t1->right = mergeTrees(t1->right, t2->right);
return t1;

2.1.2 迭代法

  • 把两个树的节点同时加入队列进行比较
  • 使用队列,模拟层序遍历

2.2 代码实现

递归法

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
        if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
        // 修改了t1的数值和结构
        t1->val += t2->val;                             // 中
        t1->left = mergeTrees(t1->left, t2->left);      // 左
        t1->right = mergeTrees(t1->right, t2->right);   // 右
        return t1;
    }
};

迭代法

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == NULL) return t2;
        if (t2 == NULL) return t1;
        queue<TreeNode*> que;
        que.push(t1);
        que.push(t2);
        while(!que.empty()) {
            TreeNode* node1 = que.front(); que.pop();
            TreeNode* node2 = que.front(); que.pop();
            // 此时两个节点一定不为空,val相加
            node1->val += node2->val;

            // 如果两棵树左节点都不为空,加入队列
            if (node1->left != NULL && node2->left != NULL) {
                que.push(node1->left);
                que.push(node2->left);
            }
            // 如果两棵树右节点都不为空,加入队列
            if (node1->right != NULL && node2->right != NULL) {
                que.push(node1->right);
                que.push(node2->right);
            }

            // 当t1的左节点 为空 t2左节点不为空,就赋值过去
            if (node1->left == NULL && node2->left != NULL) {
                node1->left = node2->left;
            }
            // 当t1的右节点 为空 t2右节点不为空,就赋值过去
            if (node1->right == NULL && node2->right != NULL) {
                node1->right = node2->right;
            }
        }
        return t1;
    }
};

3. 二叉搜索树中的搜索

代码随想录: 原文

力扣题目: 700.二叉搜索树中的搜索

3.1 思路

二叉搜索树是一个有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉搜索树

3.1.1 递归法

1. 确定递归函数的参数和返回值

  • 递归函数的参数传入的就是根节点和要搜索的数值
  • 返回的就是以这个搜索数值所在的节点
TreeNode* searchBST(TreeNode* root, int val)
  1. 确定终止条件

如果root为空,或者找到这个数值了,就返回root节点

if (root == NULL || root->val == val) return root;

  1. 确定单层递归的逻辑
  • 二叉搜索树的节点是有序的,所以可以有方向的去搜索
  • 如果root->val > val,搜索左子树
  • 如果root->val < val,就搜索右子树
  • 最后如果都没有搜索到,就返回NULL
if (root->val > val) return searchBST(root->left, val);
if (root->val < val) return searchBST(root->right, val);
return NULL ;

3.1.2 迭代法

  • 因为二叉搜索树的特殊性,也就是节点的有序性,可以不使用辅助栈或者队列就可以写出迭代法
  • 对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向

3.2 代码实现

递归法

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        if (root == NULL || root->val == val) return root;
        
        if (root->val > val) return searchBST(root->left, val);
        if (root->val < val) return searchBST(root->right, val);
        return NULL;
    }
};

迭代法

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        while (root != NULL) {
            if (root->val > val) root = root->left;
            else if (root->val < val) root = root->right;
            else return root;
        }
        return NULL;
    }
};

4. 验证二叉搜索树

代码随想录: 原文

力扣题目: 98.验证二叉搜索树

4.1 思路

  • 中序遍历下,输出的二叉搜索树节点的数值是有序序列
  • 验证二叉搜索树,就相当于判断一个序列是不是递增的

4.1.1 递归法

递归中序遍历将二叉搜索树转变成一个数组

vector<int> vec;
void traversal(TreeNode* root) {
    if (root == NULL) return;
    traversal(root->left);
    vec.push_back(root->val); // 将二叉搜索树转换为有序数组
    traversal(root->right);
}

然后比较一下,这个数组是否是有序的,注意二叉搜索树中不能有重复元素

traversal(root);
for (int i = 1; i < vec.size(); i++) {
    // 注意要小于等于,搜索树里不能有相同元素
    if (vec[i] <= vec[i - 1]) return false;
}
return true;

也在递归遍历的过程中直接判断是否有序,但比较容易陷入两个陷阱:

陷阱1

  • 不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了
  • 我们要比较的是 左子树所有节点小于中间节点右子树所有节点大于中间节点

陷阱2

  • 样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的
  • 此时可以初始化比较元素为longlong的最小值

递归三部曲:

1. 确定递归函数,返回值以及参数

  • 要定义一个longlong的全局变量,用来比较遍历的节点是否有序
  • 递归函数要有bool类型的返回值
long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
bool isValidBST(TreeNode* root)

2. 确定终止条件

二叉搜索树也可以为空

if (root == NULL) return true;

3. 确定单层递归的逻辑

  • 中序遍历,一直更新maxVal,一旦发现maxVal >= root->val,就返回false
  • 注意元素相同时候也要返回false
bool left = isValidBST(root->left);         // 左

// 中序遍历,验证遍历的元素是不是从小到大
if (maxVal < root->val) maxVal = root->val; // 中
else return false;

bool right = isValidBST(root->right);       // 右
return left && right;

4.1.2 迭代法

可以用迭代法模拟二叉树中序遍历

4.2 代码实现

递归法

class Solution {
public:
    long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
    bool isValidBST(TreeNode* root) {
        if (root == NULL) return true;

        bool left = isValidBST(root->left);
        // 中序遍历,验证遍历的元素是不是从小到大
        if (maxVal < root->val) maxVal = root->val;
        else return false;
        bool right = isValidBST(root->right);

        return left && right;
    }
};

迭代法

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        TreeNode* pre = NULL; // 记录前一个节点
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) {
                st.push(cur);
                cur = cur->left;                // 左
            } else {
                cur = st.top();                 // 中
                st.pop();
                if (pre != NULL && cur->val <= pre->val)
                return false;
                pre = cur; //保存前一个访问的结点

                cur = cur->right;               // 右
            }
        }
        return true;
    }
};

5. 总结

  • 用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销
  • 如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整
  • 迭代法中,一般一起操作两个树都是使用队列模拟类似层序遍历,同时处理两个树的节点
  • 针对二叉搜索树的题目,一定要利用其特性

学习时间:160min

0

评论区