代碼隨想錄算法訓(xùn)練營(yíng)打卡Day20 | LeetCode654 最大二叉樹(shù)赁酝、LeetCode617 合并二叉樹(shù)、LeetCode700 二叉搜索樹(shù)中的搜索旭等、LeetCode98 驗(yàn)證二叉搜索樹(shù)

摘要

  • 從序列構(gòu)造二叉樹(shù)的關(guān)鍵是要找到劃分點(diǎn)酌呆,將序列分為左子樹(shù)的序列和右子樹(shù)的序列
  • 在對(duì)二叉樹(shù)進(jìn)行訪問(wèn)前,要根據(jù)問(wèn)題確定用哪種遍歷方式進(jìn)行訪問(wèn)
  • 二叉搜索樹(shù)的定義是左子樹(shù)的所有節(jié)點(diǎn)的值小于根節(jié)點(diǎn)的值搔耕,右子樹(shù)的所有節(jié)點(diǎn)的值小于根節(jié)點(diǎn)的值隙袁;是“左子樹(shù)”和“右子樹(shù)”,而不是“左孩子”和右孩子

LeetCode654 最大二叉樹(shù)

654. 最大二叉樹(shù) - 力扣(Leetcode)

  • 初見(jiàn)題目的想法弃榨,這道題與用中序序列和后序序列構(gòu)造二叉樹(shù)有異曲同工之妙菩收,只是找劃分節(jié)點(diǎn)的規(guī)則不同:
    • 用中序序列和后序序列構(gòu)造二叉樹(shù),是將后序序列的最后一個(gè)節(jié)點(diǎn)作為根節(jié)點(diǎn)鲸睛,然后找到根節(jié)點(diǎn)在中序序列中的位置娜饵,將中序序列劃分為左子樹(shù)的序列和右子樹(shù)的序列,再通過(guò)左官辈、右子樹(shù)的序列長(zhǎng)度箱舞,在后序序列中劃分出左子樹(shù)的序列和右子樹(shù)的序列。上述過(guò)程是遞歸的拳亿。
    • 而本題定義的最大二叉樹(shù)晴股,找劃分節(jié)點(diǎn)的規(guī)則變?yōu)榱苏倚蛄兄兄底畲蟮墓?jié)點(diǎn)作為根節(jié)點(diǎn),然后將序列劃分為左子樹(shù)的序列和右子樹(shù)的序列风瘦,這個(gè)過(guò)程也是遞歸的队魏。
  • 明確了劃分序列的過(guò)程胡桨,接下來(lái)就是寫(xiě)遞歸函數(shù):
    • 傳入的參數(shù)和返回值:傳入構(gòu)造二叉樹(shù)的序列,以及當(dāng)前子樹(shù)對(duì)應(yīng)的區(qū)間的左邊界和右邊界涌哲,本題采用左閉右開(kāi)區(qū)間狗唉。返回值為當(dāng)前子樹(shù)的根節(jié)點(diǎn)肾筐。
    • 遞歸的終止條件:序列內(nèi)沒(méi)有節(jié)點(diǎn),或序列內(nèi)僅有一個(gè)節(jié)點(diǎn)(說(shuō)明到達(dá)葉節(jié)點(diǎn))
    • 單層遞歸的邏輯:首先找到序列內(nèi)值最大的節(jié)點(diǎn),記錄該節(jié)點(diǎn)在序列中的位置,并將其作為根節(jié)點(diǎn);然后,以根節(jié)點(diǎn)為分界,將序列劃分為左子樹(shù)的序列和右子樹(shù)的序列,嘗試構(gòu)造根節(jié)點(diǎn)的左子樹(shù)和右子樹(shù)。

完整的題解代碼如下

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 合并二叉樹(shù)

617. 合并二叉樹(shù) - 力扣(Leetcode)

  • 初見(jiàn)題目的想法:這道題乍一看涉及同時(shí)訪問(wèn)兩棵二叉樹(shù)瘸恼,但實(shí)際上同時(shí)訪問(wèn)的兩棵二叉樹(shù)的節(jié)點(diǎn)位置是相同的,所以邏輯上和訪問(wèn)一棵二叉樹(shù)類(lèi)似。
  • 采用前序遍歷來(lái)合并二叉樹(shù)
    • 傳入的參數(shù)和返回值:傳入兩棵樹(shù)位置”重疊的“節(jié)點(diǎn)焚刚,返回合并后的節(jié)點(diǎn)
    • 遞歸的終止條件:兩棵樹(shù)在該位置的節(jié)點(diǎn)都為空
    • 單層遞歸的的邏輯:首先合并當(dāng)前位置的兩個(gè)”重疊的”節(jié)點(diǎn)碳柱,然后嘗試合并當(dāng)前節(jié)點(diǎn)的左節(jié)點(diǎn)和右節(jié)點(diǎn)莲镣。

完整題解代碼如下福稳,

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);
    }
};

需要注意的是,上面的題解代碼還是沒(méi)有完全構(gòu)造一棵新的二叉樹(shù)瑞侮,直接return cur1return cur2會(huì)讓新的二叉樹(shù)與舊的二叉樹(shù)共享一些節(jié)點(diǎn)的圆,所以可以做以下修改

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 二叉搜索樹(shù)中的搜索

700. 二叉搜索樹(shù)中的搜索 - 力扣(Leetcode)

  • 二叉搜索樹(shù)中的搜索非常直觀,和二分搜索很相似:
    • 若目標(biāo)值比當(dāng)前節(jié)點(diǎn)的值小半火,則去訪問(wèn)當(dāng)前節(jié)點(diǎn)的左孩子越妈;若目標(biāo)值比當(dāng)前節(jié)點(diǎn)的值大,則去訪問(wèn)當(dāng)前節(jié)點(diǎn)的右孩子钮糖。
    • 從根節(jié)點(diǎn)開(kāi)始梅掠,重復(fù)上述步驟,直到找到目標(biāo)值店归,或當(dāng)前節(jié)點(diǎn)為空節(jié)點(diǎn)阎抒。

遞歸法

class Solution {
public:
    TreeNode* search(TreeNode* cur, int& val) {
        TreeNode* res = nullptr;
        // 短路判斷,cur為空則直接返回消痛,不會(huì)再判斷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 驗(yàn)證二叉搜索樹(shù)

98. 驗(yàn)證二叉搜索樹(shù) - 力扣(Leetcode)

  • 初見(jiàn)題目的想法,陷入了經(jīng)典誤區(qū)
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);
    }
};
  • 為什么會(huì)錯(cuò)肄满?這段代碼的邏輯只能保證“左孩子的值小于父節(jié)點(diǎn)的值和右孩子的值大于父節(jié)點(diǎn)的值”谴古,這只是局部的质涛。而二叉搜索樹(shù)要求的是“左子樹(shù)所有節(jié)點(diǎn)的值小于父節(jié)點(diǎn)的值和右子樹(shù)所有節(jié)點(diǎn)的值大于父節(jié)點(diǎn)的值。所以上面這段代碼乍一看沒(méi)什么問(wèn)題掰担,但是它是不符合二叉查找樹(shù)的定義的汇陆。
  • 下圖就是反例。對(duì)于每個(gè)節(jié)點(diǎn)带饱,其左孩子的都值都小于自己的值毡代,右孩子的值都大于自己的值,但是這并不能保證左子樹(shù)和右子樹(shù)也符合要求勺疼。下圖的 節(jié)點(diǎn)3 就是問(wèn)題所在教寂,3 小于 6 沒(méi)錯(cuò),但是 3 也小于 5执庐,3 實(shí)際上應(yīng)該在 5 的左子樹(shù)酪耕。下圖所示的二叉樹(shù)并不是一棵二叉查找樹(shù)。
這不是二叉搜索樹(shù)
  • 再回顧一次二叉搜索樹(shù)的定義轨淌,我開(kāi)始思考應(yīng)該用什么樣的遍歷方式更加合適
    • 二叉搜索樹(shù)是一個(gè)有序樹(shù):
      • 若它的左子樹(shù)不空迂烁,則左子樹(shù)上所有結(jié)點(diǎn)的值均小于它的根結(jié)點(diǎn)的值;
      • 若它的右子樹(shù)不空递鹉,則右子樹(shù)上所有結(jié)點(diǎn)的值均大于它的根結(jié)點(diǎn)的值盟步;
      • 它的左、右子樹(shù)也分別為二叉搜索樹(shù)
    • 二叉搜索樹(shù)的定義躏结,使得二叉搜索樹(shù)的中序遍歷序列一定是一個(gè)升序序列却盘。
  • 所以,只要在中序遍歷的過(guò)程中媳拴,驗(yàn)證當(dāng)前節(jié)點(diǎn)的值是否比中序遍歷中上一個(gè)節(jié)點(diǎn)的值大即可
    • 傳入的參數(shù)和返回值:傳入當(dāng)前節(jié)點(diǎn)和上一個(gè)節(jié)點(diǎn)谷炸。返回當(dāng)前子樹(shù)是否為二叉搜索樹(shù)。
    • 遞歸的終止條件:當(dāng)前節(jié)點(diǎn)為空禀挫,空樹(shù)是一棵二叉搜索樹(shù)旬陡,直接返回true;或者可以理解為空節(jié)點(diǎn)對(duì)是否是二叉搜索樹(shù)沒(méi)有影響语婴,可以直接返回true描孟。
    • 單層遞歸的處理邏輯:要注意是中序遍歷,所以先判斷左子樹(shù)是否是二叉搜索樹(shù)砰左,再判斷當(dāng)前節(jié)點(diǎn)的值是否大于上一個(gè)節(jié)點(diǎn)的值匿醒,然后再去判斷右子樹(shù)是否為二叉搜索樹(shù)。

完整題解代碼如下

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);
    }
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缠导,一起剝皮案震驚了整個(gè)濱河市廉羔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌僻造,老刑警劉巖憋他,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孩饼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡竹挡,警方通過(guò)查閱死者的電腦和手機(jī)镀娶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)揪罕,“玉大人梯码,你說(shuō)我怎么就攤上這事『脝” “怎么了轩娶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)框往。 經(jīng)常有香客問(wèn)我鳄抒,道長(zhǎng),這世上最難降的妖魔是什么搅窿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮隙券,結(jié)果婚禮上男应,老公的妹妹穿的比我還像新娘。我一直安慰自己娱仔,他們只是感情好沐飘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著牲迫,像睡著了一般耐朴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盹憎,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天筛峭,我揣著相機(jī)與錄音,去河邊找鬼陪每。 笑死影晓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的檩禾。 我是一名探鬼主播挂签,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盼产!你這毒婦竟也來(lái)了饵婆?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤戏售,失蹤者是張志新(化名)和其女友劉穎侨核,沒(méi)想到半個(gè)月后草穆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芹关,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年续挟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侥衬。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诗祸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轴总,到底是詐尸還是另有隱情直颅,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布怀樟,位于F島的核電站功偿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏往堡。R本人自食惡果不足惜械荷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望虑灰。 院中可真熱鬧吨瞎,春花似錦、人聲如沸穆咐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)对湃。三九已至崖叫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拍柒,已是汗流浹背心傀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拆讯,地道東北人剧包。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像往果,于是被迫代替她去往敵國(guó)和親疆液。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354