Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions 617.MergeTwoBinaryTrees/dfs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* mergeTrees(TreeNode* node1, TreeNode* node2) {
if (!node1 && !node2) {
return nullptr;
}
if (!node1) {
return node2;
}
if (!node2) {
return node1;
}
node1->val += node2->val;
node1->left = mergeTrees(node1->left, node2->left);
node1->right = mergeTrees(node1->right, node2->right);

return node1;
}
};
44 changes: 44 additions & 0 deletions 617.MergeTwoBinaryTrees/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## ステップ1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

まず再帰で書いてみるのが自然な気がします。

ルール通りすごく愚直にかいた
一つ目のnodeを用いて値を足すのか、欠損しているnodeを足すのか判定を行った

node間がoverlapしている場合つまりどちらもnullptrではに場合は、値の足し算と探索候補としてqueueに追加
2つ目のnodeのみがnullptrでない場合は、1つ目のnodeを上書き

上記を2つのnodeの要素(node)がなくなるまでループさせて最終的に一つ目のnodeを返却
30分ほど掛かった
時間計算量
O(n)

空間計算量
O(n)
## ステップ2
・queueに追加するもしくは片方のnodeにmergeする部分を関数化(MergeOrPushQueue)
 2種類の作業を1つの関数で行っているのは微妙な気がする

・MergeOrPushQueueは外側から使われないのでprivate化
 TwoNodesはメンバー関数からのみのアクセスを許可するようにprivate化

## 他の解法
dfsで解くこともできる
dfs.cppに実装
この場合も
時間計算量
O(n)

空間計算量
O(n)

新しくnodeを作る方法
TreeNodeクラスがデストラクタをどのように定義しているのか気になるところ
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TreeNodeのdtorはtrivialだと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liquo-rice
いつもありがとうございます。

A trivial destructor is a destructor that performs no action.

とございました。https://en.cppreference.com/w/cpp/language/destructor
ということは何かしら確保したリソースをdeleteする必要があるということでしょうか?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LeetCodeのTreeNodeでは、dtorが定義されていないので、defaut dtorがコンバイラによって追加されています。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liquo-rice
すみません参照した記事のすぐ下に挙動が書いてありました。失礼しました。

A trivial destructor is a destructor that performs no action. Objects with trivial destructors don't require a delete-expression and may be disposed of by simply deallocating their storage. Al

左右のノードの処理は似ているので関数化出来そうだけど、可読性が下がりそうなのでやめた
左右のどちらを作業しているのかis_leftのような引数を持たせてこれの値によって処理分けを想定

## Discord他のPRなど
新しくnodeを作る方法もある何を持って選択肢たか意識する
>新しいのを作るか作らないのか、古い入力を壊すのか壊さないのか、共有するのかしないのか(変更しない前提ならばメモリー使用量が減る)、などのオプションがあって、自分がどれを「選択」したかを意識しましょう。
https://github.com/fhiyo/leetcode/pull/25
新しく作るという手段は思いついたけど、入力と分けて管理する必要性や新しくnodeを管理するコストを考えて
片方に追加していく方式にした。
Comment on lines +41 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新しく作ることのメリット、入力を壊すデメリットについても言及しておいてもよいかもしれません

Copy link
Copy Markdown
Owner Author

@Ryotaro25 Ryotaro25 Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fhiyo ありがとうございます。
fhiyo/leetcode#25
をもういちど拝見して下記に目を通しました🙇

入力を破壊している。
https://discord.com/channels/1084280443945353267/1252267683731345438/1252556045524537344
出力を変更されるとキャッシュが変わって次からの呼び出しが狂う。
https://discord.com/channels/1084280443945353267/1252267683731345438/1252591437485441024


https://github.com/nittoco/leetcode/pull/30
86 changes: 86 additions & 0 deletions 617.MergeTwoBinaryTrees/new_node.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

privateはpublicのあとに書いたらいいでしょう。https://google.github.io/styleguide/cppguide.html#Declaration_Order

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liquo-rice
一旦structを使ったもので、privateの位置を変えました。
定義についての順序も気をつけます。
de2aa9c

struct Nodes {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このstructを作ることで特にわかりやすくなっていないように感じます。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liquo-rice
ここなのですがstructを使わないでpairなどに入れる、structの中身を親の状態を保持するTreeNodeと左右を保持するpairにするなど選択肢があるのかなと思いましたがどれが分かりやすくなっているのか判断できませんでした。

元々実装でstructを使うことで親と左右のNodeを一緒に格納できて良いなと思っておりました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メンバー変数名がイマイチなように感じました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liquo-rice
メンバー変数名を更新しました。確かに指摘の通りfirstとsecondはよくないですね。
修正後はnode、 left_child、right_childです。nodeはparent_nodeとかにしようと思いましたが単純にnodeとしました。
cd017d4

TreeNode* new_node;
TreeNode* first_node;
TreeNode* second_node;
};

public:
TreeNode* mergeTrees(TreeNode* node1, TreeNode* node2) {
if (!node1 && !node2) {
return nullptr;
}
if (!node1) {
return node2;
}
if (!node2) {
return node1;
}

TreeNode* new_root = new TreeNode(node1->val + node2->val);

queue<Nodes> search_nodes;
search_nodes.emplace(new_root, node1, node2);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

タプルのようなものを利用して、new_root, node1, node2を1つにまとめて突っ込んだ方がわかりやすいかなと思いました。

Copy link
Copy Markdown
Owner Author

@Ryotaro25 Ryotaro25 Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

今回はstructを使って指摘の三つを一緒に管理するようにしてみました。
struct Nodes { TreeNode* current_node; TreeNode* first_node; TreeNode* second_node; };
c++ではタプルより構造体を使おうとのことでしたのでstructにしました🙇
https://google.github.io/styleguide/cppguide.html#Structs_vs._Tuples

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、知らなかったですありがとうございます


while (!search_nodes.empty()) {
const auto [current_node, first_node, second_node] = std::move(search_nodes.front());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

struct Nodesの場合、move constructorもcopy constructorも同じ処理になると思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

やはりなんとなくでしか理解していないようなので、忘れていたスクラッチでのvectorを実装してmoveの動作確認してみます。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liquo-rice
指摘されていたのは以下のようなためでしょうか。

For user-defined types, the copy behavior is defined by the copy constructor and the copy-assignment operator. Move behavior is defined by the move constructor and the move-assignment operator, if they exist, or by the copy constructor and the copy-assignment operator otherwise.

https://google.github.io/styleguide/cppguide.html#Copyable_Movable_Types
自分で定義しないとコピーが行われるということですね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default move ctorはメンバー変数ごとにmoveが行われます。intやポインタなどbuilt-in型の変数の場合は、単なる代入になります。

search_nodes.pop();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queueに、nodeがNoneの時も突っ込んじゃって、pop()してから判定する方法もありますね(https://github.com/TORUS0818/leetcode/pull/25/files)
そうするとwhileの中の実装が簡潔になるだけでなく、22~30行目もいらなくなる気がします。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nittoco
レビューありがとうございます。
左右いずれかのnodeがnullptrであってもqueueに入れてwhileの中で処理する方式にてnew_node_step2.cppに実装しました。
こちらの方が少し簡潔に書けました。


// 左の子ノードを処理
TreeNode* first_left = nullptr;
TreeNode* second_left = nullptr;
if (first_node) {
first_left = first_node->left;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

基本新しくノードを作っているが、ここは元々のノードを再利用しているところが、なにか一貫性のなさを感じました。

}
if (second_node) {
second_left = second_node->left;
}
if (first_left || second_left) {
int left_val = 0;
if (first_left) {
left_val += first_left->val;
}
if (second_left) {
left_val += second_left->val;
}
current_node->left = new TreeNode(left_val);
search_nodes.emplace(current_node->left, first_left, second_left);
}

// 右の子ノードを処理
TreeNode* first_right = nullptr;
TreeNode* second_right = nullptr;
if (first_node) {
first_right = first_node->right;
}
if (second_node) {
second_right = second_node->right;
}
if (first_right || second_right) {
int right_val = 0;
if (first_right) {
right_val += first_right->val;
}
if (second_right) {
right_val += second_right->val;
}
current_node->right = new TreeNode(right_val);
search_nodes.emplace(current_node->right, first_right, second_right);
}
}

return new_root;
}
};
73 changes: 73 additions & 0 deletions 617.MergeTwoBinaryTrees/new_node_step2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* mergeTrees(TreeNode* node1, TreeNode* node2) {
TreeNode* new_root = new TreeNode();
queue<Nodes> search_nodes;
search_nodes.emplace(new_root, node1, node2);

while (!search_nodes.empty()) {
auto [current_node, left_child, right_child] = search_nodes.front();
search_nodes.pop();
if (!left_child && !right_child) {
return nullptr;
}

int current_val = 0;
if (left_child) {
current_val += left_child->val;
}
if (right_child) {
current_val += right_child->val;
}
current_node->val = current_val;

// 左の子ノードを処理
TreeNode* left_first = nullptr;
if (left_child && left_child->left) {
left_first = left_child->left;
}
TreeNode* left_second = nullptr;
if (right_child && right_child->left) {
left_second = right_child->left;
}
if (left_first || left_second) {
current_node->left = new TreeNode();
search_nodes.emplace(current_node->left, left_first, left_second);
}

// 右の子ノードを処理
TreeNode* right_first = nullptr;
if (left_child && left_child->right) {
right_first = left_child->right;
}
TreeNode* right_second = nullptr;
if (right_child && right_child->right) {
right_second = right_child->right;
}
if (right_first || right_second) {
current_node->right = new TreeNode();
search_nodes.emplace(current_node->right, right_first, right_second);
}
}

return new_root;
}

private:
struct Nodes {
TreeNode* node;
TreeNode* left_child;
TreeNode* right_child;
};
};
73 changes: 73 additions & 0 deletions 617.MergeTwoBinaryTrees/new_node_step3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* mergeTrees(TreeNode* node1, TreeNode* node2) {
if (!node1 && !node2) {
return nullptr;
}
TreeNode* new_root = new TreeNode();
queue<MergedAndOriginalNodes> search_nodes;
search_nodes.push({new_root, node1, node2});

while (!search_nodes.empty()) {
auto [current_node, first_node, second_node] = search_nodes.front();
search_nodes.pop();

int current_val = 0;
if (first_node) {
current_val += first_node->val;
}
if (second_node) {
current_val += second_node->val;
}
current_node->val = current_val;

// 左の子ノードを処理
TreeNode* first_left = nullptr;
if (first_node) {
first_left = first_node->left;
}
TreeNode* second_left = nullptr;
if (second_node) {
second_left = second_node->left;
}
if (first_left || second_left) {
current_node->left = new TreeNode();
search_nodes.emplace(current_node->left, first_left, second_left);
}

// 右の子ノードを処理
TreeNode* first_right = nullptr;
if (first_node) {
first_right = first_node->right;
}
TreeNode* second_right = nullptr;
if (second_node) {
second_right = second_node->right;
}
if (first_right || second_right) {
current_node->right = new TreeNode();
search_nodes.push({current_node->right, first_right, second_right});
}
}

return new_root;
}

private:
struct MergedAndOriginalNodes {
TreeNode* node;
TreeNode* left_child;
TreeNode* right_child;
};
};
53 changes: 53 additions & 0 deletions 617.MergeTwoBinaryTrees/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
struct TwoNodes {
TreeNode* first_node;
TreeNode* second_node;
};

TreeNode* mergeTrees(TreeNode* node1, TreeNode* node2) {
if (!node1 && !node2) {
return nullptr;
}
if (!node1) {
return node2;
}
if (!node2) {
return node1;
}

queue<TwoNodes> search_nodes;
search_nodes.emplace(node1, node2);
while (!search_nodes.empty()) {
auto [first_node, second_node] = std::move(search_nodes.front());
search_nodes.pop();

first_node->val += second_node->val;

if (first_node->left && second_node->left) {
search_nodes.emplace(first_node->left, second_node->left);
} else if (second_node->left) {
first_node->left = second_node->left;
}

if (first_node->right && second_node->right) {
search_nodes.emplace(first_node->right, second_node->right);
} else if (second_node->right) {
first_node->right = second_node->right;
}
}

return node1;
}
};
Loading