C++で解くパス合計III:DFSで二分木のルートからリーフまでの経路を探索
整数のキーを持つノードで構成される二分木が与えられたとき、合計値が指定した値と一致する「ルートからリーフ(葉)までの経路」をすべて見つける問題を考えます。経路は必ず根から始まり、葉で終わる必要があります。
問題の例
たとえば、次のような二分木 [5,4,8,11,null,13,4,7,2,null,null,5,1] があり、目標の合計値が 22 だとします。

このとき、条件を満たす経路は次の 2 本です。
[[5, 4, 11, 2], [5, 8, 4, 5]]
解法のアプローチ:DFS(深さ優先探索)+バックトラック
この問題は、少し手を加えた DFS(深さ優先探索)関数で効率的に解くことができます。dfs 関数は「現在のノード」「残りの目標合計」「これまで辿った経路を格納する一時配列」の 3 つの引数を受け取り、次のように動作します。
- ノードが存在しない場合は、そのまま戻る。
- ノードが葉(左右の子がどちらも存在しない)の場合:
- 残りの合計がそのノードの値と一致していれば、ノードの値を一時配列に追加し、完成した経路を結果に保存してから、一時配列の末尾を取り除く。
- 処理を終えて戻る。
- 葉でない場合は、ノードの値を一時配列に追加する。
- 左の子に対して dfs(左の子, 合計 − ノードの値, 一時配列) を再帰的に呼び出す。
- 右の子に対しても同様に dfs(右の子, 合計 − ノードの値, 一時配列) を呼び出す。
- 最後に一時配列の末尾要素を削除して戻る(バックトラック)。
ポイントは、子ノードへ潜るときに「合計 − 現在のノードの値」を渡すことで、残りの目標値を更新し続ける点です。また、再帰から抜ける際に一時配列の末尾を削除することで、別の経路の探索に影響を与えないようにしています。
C++による実装例
以下は、上記のアルゴリズムを C++ で実装した完全なコードです。二分木をレベル順(幅優先)に構築する補助関数や、結果を見やすく表示する関数も含まれています。
#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<vector<int> > v){
cout << "[";
for(int i = 0; i<v.size(); i++){
cout << "[";
for(int j = 0; j <v[i].size(); j++){
cout << v[i][j] << ", ";
}
cout << "],";
}
cout << "]"<<endl;
}
class TreeNode{
public:
int val;
TreeNode *left, *right;
TreeNode(int data){
val = data;
left = 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:
vector < vector <int> > res;
void dfs(TreeNode* root, int sum, vector <int>& temp){
if(!root)return;
if(!root->left && !root->right){
if(sum == root->val){
temp.push_back(root->val);
res.push_back(temp);
temp.pop_back();
}
return;
}
temp.push_back(root->val);
dfs(root->left, sum - root->val, temp);
dfs(root->right, sum - root->val, temp);
temp.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
res.clear();
vector <int> temp;
dfs(root, sum, temp);
return res;
}
};
main(){
Solution ob;
vector<int> v = {5,4,8,11,NULL,13,4,7,2,NULL,NULL,NULL,NULL,5,1};
TreeNode *root = make_tree(v);
print_vector(ob.pathSum(root, 22));
}
実行結果
入力:
[5,4,8,11,null,13,4,7,2,null,null,5,1] 22
出力:
[[5, 4, 11, 2],[5, 8, 4, 5]]
計算量の目安
すべてのノードをちょうど 1 回ずつ訪問するため、探索本体の時間計算量は O(N) です。ただし、条件を満たす経路が見つかるたびに経路のコピーを作成するため、最悪ケースでは全体の計算量が O(N²) になる点に注意してください。必要なメモリ量は再帰の深さに依存し、バランスの取れた木では O(log N)、極端に偏った木では最大 O(N) となります。
-
C++で二分木における2つの葉ノード間の最大パス合計を求める方法
問題の概要 この問題では、各ノードが値を持つ二分木が与えられます。私たちのタスクは、二分木における2つの葉ノード(リーフノード)間の最大パス合計を求めるプログラムを作成することです。 ここで求めるのは、値の合計が最大になるような、ある葉ノードから別の葉ノードへのパスです。この最大合計パスには、ルートノードが含まれる場合もあれば、含まれない場合もあります。 二分木(Binary Tree)とは、各ノードが最大2つの子ノードを持つことができる木構造のデータ構造です。それぞれの子ノードは「左の子(left child)」と「右の子(right child)」と呼ばれます。 具体例 以下のような二分木
-
Pythonで二分木のパス合計(Path Sum)を判定する方法
パス合計問題とは二分木と目標の合計値が与えられたとき、根から葉までの経路をたどった際のノード値の合計が、与えられた値と一致するような経路が存在するかどうかを判定します。例として、木が [0, -3, 9, -10, null, 5] という構成で、合計値が 14 の場合を考えてみましょう。このとき、0 → 9 → 5 という経路が存在し、その合計はちょうど 14 になるため、答えは True となります。解法のアプローチこの問題は再帰を使うことで簡潔に解くことができます。手順は以下の通りです。根ノードが null(空)の場合、False を返します。左右の子ノードが両方とも空(つまり葉ノード)