Skip to content
Merged
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
28 changes: 28 additions & 0 deletions 617/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Solve Time : 09:16

Time : O(V)
Space : O(V)

rootの宣言周りのミスで無駄に手間取ってしまった。
newをつかうべきだったがそれを失念していた。
それ以外は特に問題なくクリア、気分的にとりあえず再帰で解いた。
*/
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1 && !root2) {
return nullptr;
}
if (!root1) {
return root2;
}
if (!root2) {
return root1;
}
TreeNode *root = new TreeNode(root1->val + root2->val);
root->left = mergeTrees(root1->left, root2->left);
root->right = mergeTrees(root1->right, root2->right);
return root;
}
};
55 changes: 55 additions & 0 deletions 617/step2_0.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
Time : O(V)
Space : O(V)

step2_1の改良前
*/

class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1 && !root2) {
return nullptr;
}
stack<pair<vector<TreeNode*>, bool>> next_nodes;
auto merged_root = new TreeNode();
if (root1) {
merged_root->val += root1->val;
}
if (root2) {
merged_root->val += root2->val;
}
next_nodes.push({{(root1 ? root1->left : nullptr), (root2 ? root2->left : nullptr), merged_root}, true});
next_nodes.push({{(root1 ? root1->right : nullptr), (root2 ? root2->right : nullptr), merged_root}, false});
while (!next_nodes.empty()) {
auto [nodes, is_left] = next_nodes.top();
next_nodes.pop();
auto node1 = nodes[0];
auto node2 = nodes[1];
auto parent_node = nodes[2];
if (!node1 && !node2) {
continue;
}
int merged_val = (node1 ? node1->val : 0) + (node2 ? node2->val : 0);
auto merged_node = new TreeNode(merged_val);
if (is_left) {
parent_node->left = merged_node;
} else {
parent_node->right = merged_node;
}
if (!node2) {
next_nodes.push({{node1->left, nullptr, merged_node}, true});
next_nodes.push({{node1->right, nullptr, merged_node}, false});
continue;
}
if (!node1) {
next_nodes.push({{nullptr, node2->left, merged_node}, true});
next_nodes.push({{nullptr, node2->right, merged_node}, false});
continue;
}
next_nodes.push({{node1->left, node2->left, merged_node}, true});
next_nodes.push({{node1->right, node2->right, merged_node}, false});
}
return merged_root;
}
};
45 changes: 45 additions & 0 deletions 617/step2_1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Time : O(V)
Space : O(V)

再帰を用いない方法で書いてみる。
参考館演算子は普段なるべく使わないが、使うとnulptr考慮がかなりスッキリかけたので利用する。
Copy link

Choose a reason for hiding this comment

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

三項演算子のことですか?「普段なるべく使わない」というのは理由があったりしますか?

Copy link
Owner Author

@colorbox colorbox Jan 27, 2025

Choose a reason for hiding this comment

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

三項演算子のことですか?

三項間演算子ですね

三項間演算子は基本的に読みづらくなるので、なるべくif ~ elseを使うようにしています。
今回はそうすると異常に冗長になったので三項間演算子を使用しました。

Copy link

Choose a reason for hiding this comment

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

C++特有の事情があるのかなと思って聞いてみましたが、そういうわけではなかったのですね。ありがとうございます
「間」はいらないと思います
https://ja.wikipedia.org/wiki/%E4%B8%89%E9%A0%85%E6%BC%94%E7%AE%97%E5%AD%90

Copy link
Owner Author

Choose a reason for hiding this comment

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

三項演算子の方が主流な呼び方なんですね

*/
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1 && !root2) {
return nullptr;
}
stack<pair<vector<TreeNode*>, bool>> next_nodes;
Copy link

Choose a reason for hiding this comment

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

next_nodesの中身がwhile文の中を読むまでわからなかったので、(C++にそういうものがあれば)構造体を定義したり、コメントをつけたりするといいと思いました。構造体を使わないとしても、vectorを使っていることに違和感がありました。何かしらの方法でleft_node, right_node, merged_nodeの3つが順に入ることを表現した方がいいと思いました。

Copy link

Choose a reason for hiding this comment

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

長さが必ず 3 の vector を使うのは、軽い抵抗があります。意味としては同じものが並んでいるわけではないので。
tuple で4つ並べるのでいいのではないかなと思います。
ただ、これくらいになってくるとそろそろ構造体を定義してもよいかもしれません。

これとだいたい同じですかね。
https://discord.com/channels/1084280443945353267/1245404801177616394/1284010446306803765

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます
最後のbool含めてそれぞれ役割が異なるので、構造体でまとめる、tupleでまとめるほうが適切ですね。

auto merged_root = new TreeNode();
if (root1) {
Copy link

Choose a reason for hiding this comment

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

{0, nullptr, nullptr} な番兵を用意して、番兵のアドレスを root1 にセットすれば下の分岐がなくせますかね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

あー、なるほど、番兵を用意すると冗長な三項間演算子が消せますね、ありがとうございます。

merged_root->val += root1->val;
}
if (root2) {
merged_root->val += root2->val;
}
next_nodes.push({{(root1 ? root1->left : nullptr), (root2 ? root2->left : nullptr), merged_root}, true});
next_nodes.push({{(root1 ? root1->right : nullptr), (root2 ? root2->right : nullptr), merged_root}, false});
while (!next_nodes.empty()) {
auto [nodes, is_left] = next_nodes.top();
next_nodes.pop();
auto node1 = nodes[0];
auto node2 = nodes[1];
auto parent_node = nodes[2];
if (!node1 && !node2) {
continue;
}
int merged_val = (node1 ? node1->val : 0) + (node2 ? node2->val : 0);
auto merged_node = new TreeNode(merged_val);
if (is_left) {
parent_node->left = merged_node;
} else {
parent_node->right = merged_node;
}
next_nodes.push({{(node1 ? node1->left : nullptr), (node2 ? node2->left : nullptr), merged_node}, true});
next_nodes.push({{(node1 ? node1->right : nullptr), (node2 ? node2->right : nullptr), merged_node}, false});
}
return merged_root;
}
};
30 changes: 30 additions & 0 deletions 617/step2_2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Time : O(V)
Space : O(V)

ポインタを用いて簡略化したもの
*/
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1 && !root2) {
return nullptr;
}
stack<tuple<TreeNode*, TreeNode*, TreeNode**>> next_nodes;
auto merged_root = new TreeNode();
Copy link

Choose a reason for hiding this comment

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

ここ、nullptr でよいように思います。ループの中で中身が作られていますね。

おおむねいいと思います。
ダブルポインターによる解法はここにもあります。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.cxy3cik6kyqx

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。
確かにここの代入は不要でした。

next_nodes.push({root1, root2, &merged_root});
while (!next_nodes.empty()) {
auto [node1, node2, merged_node_ptr] = next_nodes.top();
next_nodes.pop();
if (!node1 && !node2) {
continue;
}
int merged_val = (node1 ? node1->val : 0) + (node2 ? node2->val : 0);
TreeNode* merged_node = new TreeNode(merged_val);
*merged_node_ptr = merged_node;
next_nodes.push({(node1 ? node1->left : nullptr), (node2 ? node2->left : nullptr), &merged_node->left});
next_nodes.push({(node1 ? node1->right : nullptr), (node2 ? node2->right : nullptr), &merged_node->right});
}
return merged_root;
}
};
39 changes: 39 additions & 0 deletions 617/step2_3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1 && !root2) {
return nullptr;
}
stack<pair<vector<TreeNode*>, bool>> next_nodes;
auto merged_root = new TreeNode();
if (!root1) {
root1 = new TreeNode();
}
if (!root2) {
root2 = new TreeNode();
}
merged_root->val += root1->val + root2->val;
next_nodes.push({{root1->left, root2->left, merged_root}, true});
next_nodes.push({{root1->right, root2->right, merged_root}, false});
while (!next_nodes.empty()) {
auto [nodes, is_left] = next_nodes.top();
next_nodes.pop();
auto node1 = nodes[0];
auto node2 = nodes[1];
auto parent_node = nodes[2];
if (!node1 && !node2) {
continue;
}
int merged_val = (node1 ? node1->val : 0) + (node2 ? node2->val : 0);
auto merged_node = new TreeNode(merged_val);
if (is_left) {
parent_node->left = merged_node;
} else {
parent_node->right = merged_node;
}
next_nodes.push({{(node1 ? node1->left : nullptr), (node2 ? node2->left : nullptr), merged_node}, true});
next_nodes.push({{(node1 ? node1->right : nullptr), (node2 ? node2->right : nullptr), merged_node}, false});
}
return merged_root;
}
};
24 changes: 24 additions & 0 deletions 617/step2_4.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1 && !root2) {
return nullptr;
}
stack<tuple<TreeNode*, TreeNode*, TreeNode**>> next_nodes;
TreeNode* merged_root;
next_nodes.push({root1, root2, &merged_root});
while (!next_nodes.empty()) {
auto [node1, node2, merged_node_ptr] = next_nodes.top();
next_nodes.pop();
if (!node1 && !node2) {
continue;
}
int merged_val = (node1 ? node1->val : 0) + (node2 ? node2->val : 0);
TreeNode* merged_node = new TreeNode(merged_val);
*merged_node_ptr = merged_node;
next_nodes.push({(node1 ? node1->left : nullptr), (node2 ? node2->left : nullptr), &merged_node->left});
next_nodes.push({(node1 ? node1->right : nullptr), (node2 ? node2->right : nullptr), &merged_node->right});
}
return merged_root;
}
};
15 changes: 15 additions & 0 deletions 617/step3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1) {
return root2;
}
if (!root2) {
return root1;
}
TreeNode* node = new TreeNode(root1->val + root2->val);
node->left = mergeTrees(root1->left, root2->left);
node->right = mergeTrees(root1->right, root2->right);
return node;
}
};
Copy link

@hroc135 hroc135 Jan 27, 2025

Choose a reason for hiding this comment

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

このコードだと入力の破壊はしていませんが、入力の木と出力の木でノードが共有されている場合があることに注意しないといけないと思います。例えば下のような場合(pythonコード)にroot1しか書き換えていないつもりがmerged_treeまで書き換わっているということがあります。2_1と2_2のコードだとその点大丈夫そうです

root1 = TreeNode(1)
root2 = None
merged_tree = mergeTrees(root1, root2)
print(merged_tree.val)  # 1
root1.val += 1
print(merged_tree.val)  # 2

Copy link

Choose a reason for hiding this comment

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

newする部分木としない部分木があるというのはこの関数を使う側としては怖いので、TreeNode* dupTree(TreeNode* root)などを別途作成して、きちんと全てのnodeをnewするのがよいと思います。

    if (!root1) return dupTree(root2);
    if (!root2) return dupTree(root1);

Copy link

Choose a reason for hiding this comment

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

空だった方を値0のノードを作って計算を続行するという方法もあるそうです
olsen-blue/Arai60#23 (comment)

Copy link

Choose a reason for hiding this comment

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

C++ だとメモリーリークの関係から一工夫いりますね。

class Solution {
public:
  TreeNode* mergeTrees(const TreeNode* root1, const TreeNode* root2) const {
    if (!root1 && !root2) {
      return nullptr;
    }
    if (!root1) {
        root1 = &dummy;
    }
    if (!root2) {
        root2 = &dummy;
    }
    TreeNode* node = new TreeNode(root1->val + root2->val);
    node->left = mergeTrees(root1->left, root2->left);
    node->right = mergeTrees(root1->right, root2->right);
    return node;
  }
private:
  const TreeNode dummy = {};
};

Copy link

Choose a reason for hiding this comment

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

@oda

if (!root1) {
    root1 = new TreeNode();
}

としてしまうと、tree1で使用されているメモリを解放するときに自分で作成した空のTreeNodeは解放対象にならないということでしょうか?gptに聞いても&dummyなら大丈夫な理由がいまいちよく分からなかったです

Copy link
Owner Author

Choose a reason for hiding this comment

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

newしたオブジェクトはどこかでdelete呼ばないとリークします。
ダミーとして使うroot1は、関数の外でdeleteされないのでこのままだとメモリリークしそうですね。

Copy link

Choose a reason for hiding this comment

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

なるほどです。反対にdummyはメンバ変数として定義されているのでSolutionクラスのインスタンスがdeleteされるときにdeleteされるということですか?

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうですね、私はその理解です。

Copy link

Choose a reason for hiding this comment

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

理解できました。ありがとうございます

Copy link

Choose a reason for hiding this comment

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

それでよいです。ヒープメモリーの話はどこかにありましたね。
https://discord.com/channels/1084280443945353267/1237649827240742942/1251960606077091981