C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で解く二分木の擬似回文パス問題 ― DFSによる数え方

問題の概要

ノードの値が 1 から 9 の数字である二分木を考えます。根ノードから葉ノードへ向かうあるパスについて、パスに含まれるノード値を並べ替えた結果の少なくとも1つが回文になるとき、そのパスを「擬似回文パス(pseudo-palindromic path)」と呼びます。この問題では、根から葉への擬似回文パスが全部で何本あるかを求めます。

具体例

例として、次のような二分木が与えられたとします。

C++で解く二分木の擬似回文パス問題 ― DFSによる数え方

このとき出力は 2 になります。根ノードから葉ノードへの経路は3本存在します。赤のパスは [2,3,3]、緑のパスは [2,1,1]、そして残りのパスは [2,3,1] です。このうち擬似回文パスになっているのは赤と緑の2本だけです。赤のパス [2,3,3] は [3,2,3] と並べ替えられ、緑のパス [2,1,1] は [1,2,1] と並べ替えられるため、どちらも回文を作れます。一方、[2,3,1] はどのように並べ替えても回文にできないため、対象外となります。

解法のポイント:回文の判定条件

ある数列を並べ替えて回文にできるための必要十分条件は、「奇数回出現する値が高々1種類であること」です。この性質を利用すれば、パス上の各値の出現回数を数えておき、葉に到達した時点で奇数回出現する値の種類が 0 または 1 かどうかを確認するだけで判定できます。

アルゴリズムの手順

  1. 関数 ok() を定義します。引数は出現回数を格納した配列 v です。
  2. odd := 0 と初期化します。
  3. v の各要素 it に対して、odd := odd + (it AND 1) を計算します(奇数の個数を数える)。
  4. odd が 0 または 1 であれば true を返し、それ以外は false を返します。
  5. 関数 dfs() を定義します。引数はノード node と配列 v です。
  6. node が null の場合は何もせずに戻ります。
  7. v[node の値] を 1 増やします。
  8. node の左の子と右の子がどちらも null(葉ノード)の場合:
    • ok(v) が true なら、答え ret を 1 増やします。
    • v[node の値] を 1 減らして(バックトラックして)戻ります。
  9. dfs(左の子, v) と dfs(右の子, v) を再帰的に呼び出します。
  10. 最後に v[node の値] を 1 減らして戻ります。

メインの処理では、ret := 0 と初期化し、サイズ 10 のカウント用配列 cnt を用意して dfs(root, cnt) を呼び出し、最後に ret を返します。

計算量

各ノードを一度だけ訪問するため、時間計算量は O(N)(N はノード数)、必要な追加メモリは再帰の深さとカウント配列分の O(H)(H は木の高さ)となります。

実装例

以下の C++ 実装を見ると、理解がより深まるでしょう。

#include <bits/stdc++.h>
using namespace std;
class TreeNode{
public:
    int val;
    TreeNode *left, *right;
    TreeNode(int data){
        val = data;
        left = NULL;
        right = NULL;
    }
};
void insert(TreeNode **root, int val){
    queue<TreeNode*> q;
    q.push(*root);
    while(q.size()){
        TreeNode *temp = q.front();
        q.pop();
        if(!temp->left){
            if(val != NULL)
                temp->left = new TreeNode(val);
            else
                temp->left = new TreeNode(0);
            return;
        }
        else{
            q.push(temp->left);
        }
        if(!temp->right){
            if(val != NULL)
                temp->right = new TreeNode(val);
            else
                temp->right = new TreeNode(0);
            return;
        }
        else{
            q.push(temp->right);
        }
    }
}
TreeNode *make_tree(vector<int> v){
    TreeNode *root = new TreeNode(v[0]);
    for(int i = 1; i<v.size(); i++){
        insert(&root, v[i]);
    }
    return root;
}
class Solution {
public:
    int ret;
    bool ok(vector <int>& v){
        int odd = 0;
        for (auto& it : v) {
            odd += it & 1;
        }
        return odd == 0 || odd == 1;
    }
    void dfs(TreeNode* node, vector <int>& v){
        if (!node)
            return;
        v[node->val]++;
        if (!node->left && !node->right) {
            if (ok(v))
                ret++;
            v[node->val]--;
            return;
        }
        dfs(node->left, v);
        dfs(node->right, v);
        v[node->val]--;
    }
    int pseudoPalindromicPaths (TreeNode* root) {
        ret = 0;
        vector<int> cnt(10);
        dfs(root, cnt);
        return ret;
    }
};
main(){
    Solution ob;
    vector<int> v = {2,3,1,3,1,NULL,1};
    TreeNode *root = make_tree(v);
    cout << (ob.pseudoPalindromicPaths(root));
}

入力

{2,3,1,3,1,NULL,1}

出力

2

まとめ

この問題は、DFS で根から葉へのすべての経路を探索しながら出現回数を管理し、葉に到達した時点で「奇数回出現する値が 1 種類以下」という回文の成立条件をチェックすることで効率的に解けます。バックトラックでカウントを正しく戻すことが実装上のポイントです。


  1. C++で二分木内の合計がKとなるすべてのパスを出力する方法

    この問題では、二分木と数値Kが与えられ、パス上のノード値の合計がKに等しくなるような、木の中のすべてのパスを出力することが求められます。ここでいうパスとは、木の任意のノードから始まり、任意のノードで終わることができるものです。ただし、パスは必ず親ノードから子ノードへ向かう(下向きの)方向である必要があります。また、ノードの値は正・負・ゼロのいずれであっても構いません。具体例を使って問題を理解しましょう。K = 5 の場合出力:1 3 1 3 2 1 4解き方のアプローチこの問題を解くためには、各ノードを一時的な根(ルート)として扱い、そこから下方向へ伸びる経路のうち、ノード値の合計がKになるも

  2. C++で二分木を二分探索木(BST)へ変換する方法を解説

    二分木(Binary Tree)とは二分木とは、木構造の各ノードが最大で2つの子ノードを持つことができる特別な木構造です。これらの子ノードは、それぞれ「左の子ノード」と「右の子ノード」と呼ばれます。シンプルな二分木の例は以下の通りです。二分探索木(BST)とは二分探索木(BST)は、以下のルールに従う特別な木構造です。左の子ノードの値は、常に親ノードの値より小さい右の子ノードの値は、常に親ノードの値より大きいすべてのノードが、それぞれ独立して二分探索木の性質を満たす二分探索木(BST)の例は以下の通りです。二分探索木は、検索や最小値・最大値の探索といった操作の計算量を削減するために用いられるデ