代码随想录算法训练营打卡Day20 | LeetCode654 最大二叉树、LeetCode617 合并二叉树、LeetCode700 二叉搜索树中的搜索、LeetCode98 验证二叉搜索树

摘要

  • 从序列构造二叉树的关键是要找到划分点,将序列分为左子树的序列和右子树的序列
  • 在对二叉树进行访问前,要根据问题确定用哪种遍历方式进行访问
  • 二叉搜索树的定义是左子树的所有节点的值小于根节点的值,右子树的所有节点的值小于根节点的值;是“左子树”和“右子树”,而不是“左孩子”和右孩子

LeetCode654 最大二叉树

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

  • 初见题目的想法,这道题与用中序序列和后序序列构造二叉树有异曲同工之妙,只是找划分节点的规则不同:
    • 用中序序列和后序序列构造二叉树,是将后序序列的最后一个节点作为根节点,然后找到根节点在中序序列中的位置,将中序序列划分为左子树的序列和右子树的序列,再通过左、右子树的序列长度,在后序序列中划分出左子树的序列和右子树的序列。上述过程是递归的。
    • 而本题定义的最大二叉树,找划分节点的规则变为了找序列中值最大的节点作为根节点,然后将序列划分为左子树的序列和右子树的序列,这个过程也是递归的。
  • 明确了划分序列的过程,接下来就是写递归函数:
    • 传入的参数和返回值:传入构造二叉树的序列,以及当前子树对应的区间的左边界和右边界,本题采用左闭右开区间。返回值为当前子树的根节点。
    • 递归的终止条件:序列内没有节点,或序列内仅有一个节点(说明到达叶节点)
    • 单层递归的逻辑:首先找到序列内值最大的节点,记录该节点在序列中的位置,并将其作为根节点;然后,以根节点为分界,将序列划分为左子树的序列和右子树的序列,尝试构造根节点的左子树和右子树。

完整的题解代码如下

class Solution {
public:
    TreeNode* construct(vector<int>& nums, int l, int r) {// [l, r)
        if (l >= r) return nullptr;
        if (r - l == 1) return new TreeNode(nums[l]);
        int maxVal = INT_MIN;
        int delim = 0;
        for (int i = l; i < r; i++) {
            if (nums[i] > maxVal) {
                maxVal = nums[i];
                delim = i;
            }
        }
        TreeNode* cur = new TreeNode(nums[delim]);
        cur->left = construct(nums, l, delim);
        cur->right = construct(nums, delim + 1, r);
        return cur;
    }
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return construct(nums, 0, nums.size());
    }
};

LeetCode617 合并二叉树

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

  • 初见题目的想法:这道题乍一看涉及同时访问两棵二叉树,但实际上同时访问的两棵二叉树的节点位置是相同的,所以逻辑上和访问一棵二叉树类似。
  • 采用前序遍历来合并二叉树
    • 传入的参数和返回值:传入两棵树位置”重叠的“节点,返回合并后的节点
    • 递归的终止条件:两棵树在该位置的节点都为空
    • 单层递归的的逻辑:首先合并当前位置的两个”重叠的”节点,然后尝试合并当前节点的左节点和右节点。

完整题解代码如下,

class Solution {
public:
    TreeNode* merge(TreeNode* cur1, TreeNode* cur2) {
        if (!cur1 && !cur2) return nullptr;
        if (cur1 && !cur2) return cur1;
        if (!cur1 && cur2) return cur2;

        TreeNode* mergedNode = new TreeNode(cur1->val + cur2->val);
        mergedNode->left = merge(cur1->left, cur2->left);
        mergedNode->right = merge(cur1->right, cur2->right);

        return mergedNode;
    }
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        return merge(root1, root2);
    }
};

需要注意的是,上面的题解代码还是没有完全构造一棵新的二叉树,直接return cur1return cur2会让新的二叉树与旧的二叉树共享一些节点,所以可以做以下修改

class Solution {
public:
    TreeNode* merge(TreeNode* cur1, TreeNode* cur2) {
        if (!cur1 && !cur2) return nullptr;
        if (cur1 && !cur2) return new TreeNode(cur1->val, cur1->left, cur1->right);
        if (!cur1 && cur2) return new TreeNode(cur2->val, cur2->left, cur2->right);

        TreeNode* mergedNode = new TreeNode(cur1->val + cur2->val);
        mergedNode->left = merge(cur1->left, cur2->left);
        mergedNode->right = merge(cur1->right, cur2->right);

        return mergedNode;
    }
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        return merge(root1, root2);
    }
};

LeetCode700 二叉搜索树中的搜索

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

  • 二叉搜索树中的搜索非常直观,和二分搜索很相似:
    • 若目标值比当前节点的值小,则去访问当前节点的左孩子;若目标值比当前节点的值大,则去访问当前节点的右孩子。
    • 从根节点开始,重复上述步骤,直到找到目标值,或当前节点为空节点。

递归法

class Solution {
public:
    TreeNode* search(TreeNode* cur, int& val) {
        TreeNode* res = nullptr;
        // 短路判断,cur为空则直接返回,不会再判断cur->val == val,避免操作空指针
        if (!cur || cur->val == val) return cur;
        if (val < cur->val) res = search(cur->left, val);
        if (val > cur->val) res = search(cur->right, val);
        return res;
    }
    TreeNode* searchBST(TreeNode* root, int val) {
        return search(root, val);
    }
};

迭代法

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

LeetCode98 验证二叉搜索树

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

  • 初见题目的想法,陷入了经典误区
class Solution {
public:
    bool isValid(TreeNode* cur) {
        if (cur->left && cur->right) {
            if (cur->left->val >= cur->val || cur->right->val <= cur->val) return false;
            else return isValid(cur->left) && isValid(cur->right); 
        }
        if (cur->left && !cur->right) {
            if (cur->left->val >= cur->val) return false;
            return isValid(cur->left);
        }
        if (!cur->left && cur->right) {
            if (cur->right->val <= cur->val) return false;
            return isValid(cur->right);
        }
        return true;
    }
    bool isValidBST(TreeNode* root) {
        if (!root) return true;
        else return isValid(root);
    }
};
  • 为什么会错?这段代码的逻辑只能保证“左孩子的值小于父节点的值和右孩子的值大于父节点的值”,这只是局部的。而二叉搜索树要求的是“左子树所有节点的值小于父节点的值和右子树所有节点的值大于父节点的值。所以上面这段代码乍一看没什么问题,但是它是不符合二叉查找树的定义的。
  • 下图就是反例。对于每个节点,其左孩子的都值都小于自己的值,右孩子的值都大于自己的值,但是这并不能保证左子树和右子树也符合要求。下图的 节点3 就是问题所在,3 小于 6 没错,但是 3 也小于 5,3 实际上应该在 5 的左子树。下图所示的二叉树并不是一棵二叉查找树。
这不是二叉搜索树
  • 再回顾一次二叉搜索树的定义,我开始思考应该用什么样的遍历方式更加合适
    • 二叉搜索树是一个有序树:
      • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
      • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
      • 它的左、右子树也分别为二叉搜索树
    • 二叉搜索树的定义,使得二叉搜索树的中序遍历序列一定是一个升序序列
  • 所以,只要在中序遍历的过程中,验证当前节点的值是否比中序遍历中上一个节点的值大即可
    • 传入的参数和返回值:传入当前节点和上一个节点。返回当前子树是否为二叉搜索树。
    • 递归的终止条件:当前节点为空,空树是一棵二叉搜索树,直接返回true;或者可以理解为空节点对是否是二叉搜索树没有影响,可以直接返回true
    • 单层递归的处理逻辑:要注意是中序遍历,所以先判断左子树是否是二叉搜索树,再判断当前节点的值是否大于上一个节点的值,然后再去判断右子树是否为二叉搜索树。

完整题解代码如下

class Solution {
public:
    bool isValid(TreeNode* cur, TreeNode*& pre) {
        if (!cur) return true;
        bool left = isValid(cur->left, pre);
        if (pre && pre->val >= cur->val) return false;
        pre = cur;
        bool right = isValid(cur->right, pre);
        return left && right;
    }
    bool isValidBST(TreeNode* root) {
        TreeNode* pre = nullptr;
        return isValid(root, pre);
    }
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339