「Leetcode」101。対称バイナリツリー

101.対称バイナリツリー

バイナリツリーが与えられたら、それがミラー対称であるかどうかを確認します。

 

 

アイデア

「最初に明確に考えて、対称バイナリツリーで比較する2つのノードを決定します。比較する左右のノードではありません!」

バイナリツリーが対称であるかどうかについては、ルートノードの左側のサブツリーと右側のサブツリーが反転しているかどうかを比較する必要があります。この点を理解してください。「実際には、2つのツリーを比較します(2つのツリーはルートノードです)。左右のサブツリー)」、したがって、再帰的トラバーサルのプロセスでは、2つのツリーを同時にトラバースする必要があります。

では、比較するとどうなるでしょうか。

比較は、2つのサブツリーの内側と外側の要素が等しいかどうかです。写真が示すように:

 

 

では、トラバーサルの順序はどうあるべきでしょうか?

2つのサブツリーの内側ノードと外側ノードが等しいかどうかを再帰関数の戻り値で判断する必要があるため、この質問のトラバーサルは「ポストオーダートラバーサル」のみになります。

「2本の木を横断して内側と外側のノードを比較する必要があるからです。正確には、木の横断順序は左右中央であり、木の横断順序は右左中央です。」

ただし、厳密にはツリー上でのポストオーダートラバーサルではありませんが、ポストオーダートラバースとして理解できます。

実際、ポストオーダーは一種のバックトラックとしても理解できますが、もちろんこれは逸脱であり、バックトラックについて話すときに焦点を当てます。

これに関しては、誰もが私が少し長蛇の列になっていると感じるかもしれません。どうしてそんなに多くの真実があるのでしょうか。心配しないでください、私が言ったことは以下のコードの説明にあります。

それでは、再帰的なコードを書く方法を見てみましょう。

再帰

再帰的三部作

  1. 再帰関数のパラメーターと戻り値を決定します

比較したいのは、ルートノードの2つのサブツリーが互いに反転しているかどうかであり、ツリーが対称ツリーであるかどうかを判断するため、比較する2つのツリーは、当然、左側のサブツリーノードと右側のサブツリーノードです。

戻り値は当然boolタイプです。

コードは次のように表示されます。

bool compare(TreeNode* left, TreeNode* right)
  1. 終了条件を決定する

同じではない2つのノードの値を比較するには、最初に2つのノードが空であることを明確にする必要があります!それ以外の場合は、後で値を比較するときにnullポインタが操作されます。

ノードが空の場合は次のとおりです。(「実際には左の子と右の子を比較していないので、次のように左のノードと右のノードと呼びます」

  • 左側のノードは空です、右側のノードは空ではありません、非対称、falseを返します
  • 左は空ではなく、右は空で、非対称はfalseを返します
  • 左と右は空で対称であり、trueを返します

この時点で、ノードが空であるという状況は解消されているため、左右のノードは空ではありません。

  • 左と右の両方が空ではありません。ノード値を比較し、同じでない場合はfalseを返します

現時点では、左右のノードは空ではなく、値は同じではありません。状況にも対処します。

コードは次のように表示されます。

if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true; 
else if (left->val != right->val) return false; // 注意这里我没有使用else

上記の最後のケースでは、elseを使用しなかったことに注意してください。ただし、上記のすべてを削除した後、残っているのは、左右のノードが空ではなく、値が同じである場合です。

  1. 単一再帰のロジックを決定する

このとき、単層再帰のロジックが入力されます。単層再帰のロジックは、右側のノードが空ではなく、値が同じである状況に対処することです。

  • バイナリツリーの外側が対称であるかどうかを比較します。渡されるのは、左側のノードの左側の子と、右側のノードの右側の子です。
  • 内部テストが対称的であるかどうかを比較し、左側のノードの右側の子と右側のノードの左側の子に合格します。
  • 左側と右側が対称の場合はtrueを返し、一方が非対称の場合はfalseを返します。

コードは次のように表示されます。

bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左
bool isSame = outside && inside;                    // 左子树:中、 右子树:中(逻辑处理)
return isSame;

上記のコードでは、使用されているトラバーサル方法を確認できます。左側のサブツリーは左右、右側のサブツリーは左右なので、このトラバーサルシーケンスを「ポストオーダートラバーサル」とも呼びます(厳密にはポストオーダートラバーサルではありません)。

最終的な再帰C ++全体のコードは次のとおりです。

class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right) {
        // 首先排除空节点的情况
        if (left == NULL && right != NULL) return false;
        else if (left != NULL && right == NULL) return false;
        else if (left == NULL && right == NULL) return true;
        // 排除了空节点,再排除数值不相同的情况
        else if (left->val != right->val) return false;

        // 此时就是:左右节点都不为空,且数值相同的情况
        // 此时才做递归,做下一层的判断
        bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右
        bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左
        bool isSame = outside && inside;                    // 左子树:中、 右子树:中 (逻辑处理)
        return isSame;

    }
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        return compare(root->left, root->right);
    }
};

「私が与えたコードは簡潔ではありませんが、判断の各ステップの論理が明確に描かれています。」

インターネット上のさまざまな簡潔なコードを見ると、非常に単純に見えますが、多くのロジックがカバーされており、問題の解決策ではカバーされたロジックを明確に説明できない場合があります。

「盲目的にそれをコピーすると、結果は次のようになります。これは「単純な質問」であり、ばかげて合格しましたが、判断のすべてのステップの実際の論理は明確ではない可能性があります。」

もちろん、上記のコードを次のように整理できます。

class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right) {
        if (left == NULL && right != NULL) return false;
        else if (left != NULL && right == NULL) return false;
        else if (left == NULL && right == NULL) return true;
        else if (left->val != right->val) return false;
        else return compare(left->left, right->right) && compare(left->right, right->left);

    }
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        return compare(root->left, root->right);
    }
};

「このコードは非常に簡潔ですが、多くの論理を隠し、不明確であり、再帰的な三部作はここでは完全に見えません。」

「ですから、質問をするときは、論理と各ステップで何をすべきかを明確に考える必要があります。質問のすべての条件を考え、対応するコードを記述したら、簡潔なコードの効果を追求できます。」

反復法

このトピックには反復法を使用することもできますが、この質問の本質は2つのツリーが反転しているかどうかを判断することであり、実際にはいわゆるバイナリツリートラバーサルではないため、ここでの反復法は1次、中次、および後次の反復書き込み方法ではないことに注意してください。最初、中間、最後の関係。

ここでは、キューを使用して、2つのツリー(ルートノードの左右のサブツリー)が互いに反転しているかどうかを比較できます(「これはシーケンストラバーサルではないことに注意してください」)。

キューを使用

アニメーションに示すように、キューを使用して、ルートノードの左側と右側のサブツリーの内側と外側が等しいかどうかを判断します。

 

 

次の条件付き判断と再帰的ロジックは同じです。

コードは次のように表示されます。

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        queue<TreeNode*> que;
        que.push(root->left);   // 将左子树头结点加入队列
        que.push(root->right);  // 将右子树头结点加入队列
        while (!que.empty()) {  // 接下来就要判断这这两个树是否相互翻转
            TreeNode* leftNode = que.front(); que.pop();    
            TreeNode* rightNode = que.front(); que.pop();
            if (!leftNode && !rightNode) {  // 左节点为空、右节点为空,此时说明是对称的
                continue;
            }

            // 左右一个节点不为空,或者都不为空但数值不相同,返回false
            if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) { 
                return false;
            }
            que.push(leftNode->left);   // 加入左节点左孩子
            que.push(rightNode->right); // 加入右节点右孩子
            que.push(leftNode->right);  // 加入左节点右孩子
            que.push(rightNode->left);  // 加入右节点左孩子
        }
        return true;
    }
};

スタックを使用する

注意すれば、この反復方法では、実際には、左右のサブツリーで比較する要素を順番にコンテナに入れてから、ペアで取り出して比較することができます。実際、スタックを使用することもできます。

キューをそのままスタックに変更するだけで、以下のコードも提供します。

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        stack<TreeNode*> st; // 这里改成了栈
        st.push(root->left);
        st.push(root->right);
        while (!st.empty()) {
            TreeNode* leftNode = st.top(); st.pop();
            TreeNode* rightNode = st.top(); st.pop();
            if (!leftNode && !rightNode) {
                continue;
            }
            if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
                return false;
            }
            st.push(leftNode->left);
            st.push(rightNode->right);
            st.push(leftNode->right);
            st.push(rightNode->left);
        }
        return true;
    }
};

総括する

今回は、バイナリツリーの「単純な問題」を深く分析しました。問題を実際に理解するのは簡単ではないことがわかります。リートコードを受け入れることとそれを習得することの間にはまだギャップがあります。

再帰的手法と反復的手法を導入しましたが、再帰的三部作でこの問題を解決します。凝縮されたコードだけを見ると、再帰的三部作がどのように問題を解決するかがわかりません。

反復的な方法では、キューを使用します。これはシーケンストラバーサルではなく、比較する要素をペアで格納するためにコンテナのみが使用されることに注意してください。この本質を理解した後、キューを使用し、スタックを使用し、さらには配列を使用できます。

すでにこのトピックを実行している場合は、記事を読んだ後でこのトピックをもう一度見て、考えてみると、何か違うことがわかります。

この記事:https//github.com/youngyangyang04/leetcode-masterはすでに含まれていますが、レイダースのリートコードブラシ、各タイプのクラシックトピックのタイトルシーケンスのブラシ、マインドマップ、独自の倉庫にフォークできる、空の外観などの質問もありますあなたは間違いなく何かを得るでしょう。それがあなたを助けるなら、それをサポートするために星を与えてください

私のBステーション(私が説明したアルゴリズムビデオとプログラミング関連の知識があります)https//space.bilibili.com/525438321

私はプログラマーであり、Harbin Engineeringの兄であるCarlです。TencentとBaiduの技術研究開発に長年携わっています。空き時間を使ってリートコードをブラッシングしました。さらに エキサイティングなアルゴリズムの記事が あります:  Code Random Thoughts。注意を払っ後、「Java」「C ++」「python」と返信してください。    「テンプレートの再開」など。私が長年編集した学習資料を使用して、 WeChat 追加し   、「個人プロフィール」+「グループレビューの質問」にコメントして、レビューグループに引き込むことができます(広告なし、純粋な個人共有)。私は毎日古典的なトピックを分析しています。私が選択するすべてのトピックは孤立しているわけではなく、浅いものから深いものまであります。リズムに従って各記事を継続的に読むと、間違いなく統合されます。

おすすめ

転載: blog.csdn.net/youngyangyang04/article/details/108889652