代码随想录算法训练营第20天 | 654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树

TFTree 发布于 2022-12-05 460 次阅读


AI 摘要

这篇文章总结了LeetCode算法训练营第20天的4道题目:654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树。对于最大二叉树,文章给出了使用递归法的思路,每次进入递归的时候先找出最大值,然后用下标索引最大值左侧的数组和最大值右侧的数组,将最大值作为该层循环的根节点root传递回去。对于合并二叉树,文章同样使用递归法,递归处理的逻辑是 : 如果两个树此处都空,则返回空节点,如果有一个非空,则返回非空节点的值,如果均非空则返回两个节点值的和。文章给出了AC代码和标准答案的比较。

654. 最大二叉树 - 力扣(LeetCode)

思路:使用递归法,每次进入递归的时候先找出最大值,然后用下标索引最大值左侧的数组和最大值右侧的数组,将最大值作为该层循环的根节点root传递回去,root左侧数组的最大值为其左孩子,右侧数组的最大值为其右孩子

我的AC代码

//时间复杂度O(nlogn),空间复杂度O(n)
class Solution {
public:
    TreeNode* biggest(vector<int>& nums, int nbegin, int nend) {
        int nsize = nend - nbegin;
        if(nsize == 0) {
            return nullptr;
        }
        int mmax = nbegin;
        for(int i = nbegin; i < nend; ++i) {
            if(nums[i] > nums[mmax]) {
                mmax = i;
            }
        }
        TreeNode* root = new TreeNode(nums[mmax]);
        if(nsize == 1) {
            return root;
        }

        int leftbegin = nbegin;
        int leftend = mmax;

        int rightbegin = mmax + 1;
        int rightend = nend;

        root->left = biggest(nums, leftbegin, leftend);
        root->right = biggest(nums, rightbegin, rightend);

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

标准答案

//时间复杂度O(nlogn),空间复杂度O(n)
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());
    }
};

617. 合并二叉树 - 力扣(LeetCode)

思路:使用递归法,每次递归处理的逻辑是 : 如果两个树此处都空,则返回空节点,如果有一个非空,则返回非空节点的值,如果均非空则返回两个节点值的和

建议使用标准答案的递归写法,我的写法太多冗余逻辑了

我的AC代码

//时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    TreeNode* build(TreeNode* root1, TreeNode* root2) {
        if(!root1 && !root2) {
            return nullptr;
        }
        TreeNode* root;
        if(!root1 && root2) {
            root = new TreeNode(root2->val);
            root->left = build(root1, root2->left);
            root->right = build(root1, root2->right);
        }
        if(root1 && !root2) {
            root = new TreeNode(root1->val);
            root->left = build(root1->left, root2);
            root->right = build(root1->right, root2);
        }
        if(root1 && root2) {
            root = new TreeNode(root1->val + root2->val);
            root->left = build(root1->left, root2->left);
            root->right = build(root1->right, root2->right);
        }
                return root;
    }
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        return build(root1, root2);
    }
};

标准答案

递归法

//时间复杂度O(n),空间复杂度O(n)
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;
    }
};

迭代法

//时间复杂度O(n),空间复杂度O(n)
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;
    }
};

指针秀操作

//时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    void process(TreeNode** t1, TreeNode** t2) {
        if ((*t1) == NULL && (*t2) == NULL) return;
        if ((*t1) != NULL && (*t2) != NULL) {
            (*t1)->val += (*t2)->val;
        }
        if ((*t1) == NULL && (*t2) != NULL) {
            *t1 = *t2;
            return;
        }
        if ((*t1) != NULL && (*t2) == NULL) {
            return;
        }
        process(&((*t1)->left), &((*t2)->left));
        process(&((*t1)->right), &((*t2)->right));
    }
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        process(&t1, &t2);
        return t1;
    }
};

700. 二叉搜索树中的搜索 - 力扣(LeetCode)

思路:类似二分查找,如果val大于root就找右子树,如果val小于root就找左子树,如果相等就返回root

因本题比较简单,故推荐使用迭代法

我的AC代码

递归法

//时间复杂度O(logn),空间复杂度O(logn)
class Solution {
public:
    TreeNode* findval(TreeNode* root, int val) {
        if(root == nullptr) {
            return nullptr;
        }
        if(root->val == val) {
            return root;
        }
        if(root->val < val) {
            return findval(root->right, val);
        }
        if(root->val > val) {
            return findval(root->left, val);
        }
        return nullptr;
    }
    TreeNode* searchBST(TreeNode* root, int val) {
        return findval(root, val);
    }
};

迭代法

//时间复杂度O(logn),空间复杂度O(logn)
class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        TreeNode* cur = root;
        while(cur != nullptr) {
            if(val == cur->val) {
                break;
            }
            else if(val > cur->val) {
                cur = cur->right;
            }
            else if(val < cur->val) {
                cur = cur->left;
            }
        }
        return cur;
    }
};

标准答案

递归法

//时间复杂度O(logn),空间复杂度O(logn)
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;
    }
};

迭代法

//时间复杂度O(logn),空间复杂度O(logn)
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;
    }
};

98. 验证二叉搜索树 - 力扣(LeetCode)

思路:因为二叉搜索树的性质是左子树所有值都小于根节点的值,右子树所有值都大于根节点的值,所以我们可以利用二叉搜索树的这一性质,中序遍历二叉树,看遍历结果是否为递增数列,如果是递增数列则说明是二叉搜索树

  • 一个最简单的想法就是先不管三七二十一,直接中序遍历一遍二叉搜索树,然后遍历一遍结果数组,如果递增则说明是二叉搜索树
  • 但实际上这种方法会消耗更多的时间复杂度和空间复杂度,我们可以在遍历过程中就判断是否递增,即建立一个pre变量用来保存上一次遍历到的节点,如果该节点的值大于等于现在这个节点的值,那么就说明不符合,返回false,如果遍历完成都没有问题,则说明是二叉搜索树,返回true
    • 至于这边为什么选择建立一个新的pre变量,而不是每次与一个最大值(如果是二叉搜索树,每遍历到一个节点,该节点都会是最新的最大值)相比较,是因为可能测试样例中会有INT_MIN,导致我们最开始无法选定最小值,虽然我们可以采取定义lONG_MIN的方法来解决,但毕竟不够有普适性,即代码不够健壮,所以这边强烈推荐使用建立pre变量的办法
  • 不论是递归还是迭代,只要实现中序遍历即可
  • 综上我们可以看出,解决二叉搜索树问题的致胜关键是灵活运用其性质,而方法就是进行中序遍历

我的AC代码

递归法(构造数组)

//时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    void travel(TreeNode* node, vector<int>& tra) {
        if(node == nullptr) {
            return;
        }
        if(node->left != nullptr) {
            travel(node->left, tra);
        }
        tra.push_back(node->val);
        if(node->right != nullptr) {
            travel(node->right, tra);
        }
    }
    bool isValidBST(TreeNode* root) {
        if(root == nullptr) {
            return true;
        }
        vector<int> tra;
        travel(root, tra);
        int tsize = tra.size();
        for(int i = 0; i < tsize - 1; ++i) {
            if(tra[i] >= tra[i + 1]) {
                return false;
            }
        }
        return true;
    }
};

统一迭代法(中序遍历)

//时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    bool isValidBST(TreeNode* root) {
        stack<TreeNode*> st;
        if(root == nullptr) {
            return true;
        }
        st.push(root);
        TreeNode* pre = nullptr;
        while(!st.empty()) {
            TreeNode* tmp = st.top();
            if(tmp != nullptr) {
                st.pop();
                if(tmp->right) {
                    st.push(tmp->right);
                }
                st.push(tmp);
                st.push(nullptr);
                if(tmp->left) {
                    st.push(tmp->left);    
                }
            }
            else {
                st.pop();
                TreeNode* node = st.top();
                st.pop();
                if(pre != nullptr && pre->val >= node->val) {
                    return false;
                }
                pre = node;
            }
        }
        return true;
    }
};

标准答案

递归法(构造数组)

//时间复杂度O(n),空间复杂度O(n)
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;
    }
};

递归法(直接比较)

//时间复杂度O(n),空间复杂度O(n)

//版本1
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;
    }
};

//版本2
class Solution {
public:
    TreeNode* pre = NULL; // 用来记录前一个节点
    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;
    }
};

迭代法(中序遍历)

//时间复杂度O(n),空间复杂度O(n)
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;
    }
};