C++で最も頻出する部分木の合計を求めるアルゴリズム
木のルートが与えられたとき、最も頻繁に出現する「部分木の合計」を求める問題を考えてみましょう。あるノードにおける部分木の合計とは、そのノードを根とする部分木に含まれるすべてのノード値(そのノード自身を含む)の総和のことです。最頻出の部分木の合計が複数あり同率となった場合は、どのような順序でもよいので、最大頻度を持つすべての値を返します。
たとえば、木が [5,2,-5] で構成されている場合、答えは [2] となります。これは、葉ノード 2 の合計値と、根 5 の部分木全体の合計値(5 + 2 + (-5) = 2)が一致して 2 が 2 回出現する一方、-5 は 1 回しか出現しないためです。
解法のアプローチ
この問題を解くために、以下の手順に従います。
- 2つのマップ m と freq を定義します。m は整数キーとそれに対応するリストの集合を保持し、freq は各数値の出現回数を記録します。
- 木のノードを受け取る solve() メソッドを定義します。このメソッドは以下のように動作します。
- ノードが NULL の場合は 0 を返します。
- leftSum := solve(ノードの左の子)、rightSum := solve(ノードの右の子) を再帰的に計算します。
- currSum := ノードの値 + leftSum + rightSum とします。
- freq に currSum がまだ存在しない場合は、m[1] のリストに currSum を挿入し、freq[currSum] := 1 を設定します。
- すでに存在する場合は、freq[currSum] を 1 増やし、m[freq[currSum]] のリストに currSum を挿入します。
- 最後に currSum を返します。
メインの処理は以下の流れになります。
- ルートが NULL の場合は空の集合を返します。
- solve(root) を呼び出して全ノードを走査します。
- マップ m の最後(最大頻度)のリストを結果として返します。
マップ m はキーが出現回数、値がその回数で出現した合計値のリストとなるため、最後に登録されたキー(最高頻度)に対応するリストを取得すれば答えが得られます。理解を深めるために、以下の実装例を見てみましょう。
実装例
#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<auto> v){
cout << "[";
for(int i = 0; i<v.size(); i++){
cout << v[i] << ", ";
}
cout << "]"<<endl;
}
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:
map <int, vector <int> > m;
map <int, int > freq;
int solve(TreeNode* node){
if(!node)return 0;
int leftSum = solve(node->left);
int rightSum = solve(node->right);
int currSum = node->val + leftSum + rightSum;
//cout << currSum << endl;
if(!freq.count(currSum)){
m[1].push_back(currSum);
freq[currSum] = 1;
//cout << "Adding " << currSum << " 1" << endl;
} else {
freq[currSum]++;
m[freq[currSum]].push_back(currSum);
}
return currSum;
}
vector<int> findFrequentTreeSum(TreeNode* root) {
m.clear();
freq.clear();
if(!root)return {};
solve(root);
return m.rbegin()->second;
}
};
main(){
vector<int> v = {5,2,-5};
TreeNode *tree = make_tree(v);
Solution ob;
print_vector(ob.findFrequentTreeSum(tree));
}
入力
[5,2,-5]
出力
[2]
-
C++で二分木における最大部分木の合計を求める方法
この問題では、二分木(バイナリツリー)が与えられます。私たちのタスクは、木の中で最も大きな合計値を持つ部分木を見つけることです。 問題の概要 二分木には正の値と負の値が混在しています。その中から、ノードの合計が最大になる部分木を特定する必要があります。 例で問題を理解しよう 出力: 13 説明: 左部分木の合計:7 右部分木の合計:1 木全体の合計:13 このように、根を含む木全体の合計である「13」が最大の部分木の合計となります。 解法のアプローチ この問題を解くためには、後順走査(ポストオーダー走査)を利用します。手順は以下の通りです。 左部分木と右部分木それぞれのノードの合計を再
-
C++で反転木がtargetの部分木と一致するかを判定する方法
ここでは、source と target という2つの二分木が与えられ、source を反転(inversion)した木 T のうちの何れかが target の部分木になっているかどうかを判定する問題を扱います。言い換えれば、target の中に、T と値および構造が完全に一致し、そのすべての子孫ノードまで含めて同一であるようなノードが存在するかを確認するということです。反転木とは?ある木が別の木の「反転」であるとは、次のいずれかの条件を満たす場合を指します。両方の木が空(ヌル)である左右の子を必要に応じてスワップしてもよく、かつその左部分木と右部分木が互いに反転関係にある例として、入力が次の