diff --git a/tree-bst/617.md b/tree-bst/617.md index e69de29..0b0aeb2 100644 --- a/tree-bst/617.md +++ b/tree-bst/617.md @@ -0,0 +1,241 @@ +- 問題: [617. Merge Two Binary Trees](https://leetcode.com/problems/merge-two-binary-trees/description/) +- 方針 + - 再帰 + - まあ書ける + - 時間計算量, 空間計算量: O(N) root1, root2 の大きい方に依存する + - スタックオーバーフローが発生する可能性を考え始めないといけない + - Queue + - root1, root2 で和をとるべき相対位置はQueueを使うと管理しやすいなということで、パッと思いついたが、じゃあノードの張り替えをどうするか?で完全に詰まってしまった + - claude が綺麗にしてくれたけど、動かない途中のコードも貼っておく + - Stack + - Stack も Queue と似たようなことになりそうなので、今回は書かない + + +## 再帰 +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { + if (root1 == null && root2 == null) { + return null; + } + + int newVal = 0; + if (root1 != null) { + newVal += root1.val; + } + if (root2 != null) { + newVal += root2.val; + } + + TreeNode mergeTree = new TreeNode(newVal); + if (root1 == null) { + mergeTree.left = mergeTrees(null, root2.left); + mergeTree.right = mergeTrees(null, root2.right); + } else if (root2 == null) { + mergeTree.left = mergeTrees(root1.left, null); + mergeTree.right = mergeTrees(root1.right, null); + } else { + mergeTree.left = mergeTrees(root1.left, root2.left); + mergeTree.right = mergeTrees(root1.right, root2.right); + } + + return mergeTree; + } +} +``` + +- 条件分岐が冗長なので削る、最後の else ブロックが残せるようにするにはどうしたらいいかを考えると、、 + +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { + if (root1 == null) { + return root2; + } + if (root2 == null) { + return root1; + } + + TreeNode mergeTree = new TreeNode(root1.val + root2.val); + mergeTree.left = mergeTrees(root1.left, root2.left); + mergeTree.right = mergeTrees(root1.right, root2.right); + + return mergeTree; + } +} +``` + +## Queue +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { + if (root1 == null) { + return root2; + } + if (root2 == null) { + return root1; + } + + Queue nodes1 = new ArrayDeque<>(); + nodes1.add(root1); + Queue nodes2 = new ArrayDeque<>(); + nodes2.add(node2); + Queue newNode = new ArrayDeque<>(); + + while (!nodes1.isEmpty() || !nodes2.isEmpty()) { + TreeNode node1 = nodes1.poll(); + TreeNode node2 = nodes2.poll(); + + int newVal = 0; + if (node1 != null) { + newVal += node1.val; + nodes1.add(node1.left); + nodes1.add(node1.right); + } + if (node2 != null) { + newVal += node2.val; + nodes2.add(node2.left); + nodes2.add(node2.right); + } + + TreeNode newNode = new TreeNode(newVal); + newNode.add(newNode); + + while (!newNode.isEmpty()) { + TreeNode node = newNode.poll(); + } + } + } +} +``` +- claude に修正させたもの +- これは root1 を破壊的に変更するものなので私は良くないと思う +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode() {} + * TreeNode(int val) { this.val = val; } + * TreeNode(int val, TreeNode left, TreeNode right) { + * this.val = val; + * this.left = left; + * this.right = right; + * } + * } + */ +class Solution { + public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { + if (root1 == null) return root2; + if (root2 == null) return root1; + // root1のノードとroot2のノードを常にペアで管理する + Queue queue = new ArrayDeque<>(); + queue.add(new TreeNode[]{root1, root2}); + + while (!queue.isEmpty()) { + TreeNode[] pair = queue.poll(); + TreeNode n1 = pair[0]; + TreeNode n2 = pair[1]; + + // n1にn2をマージしていく + n1.val += n2.val; + + if (n1.left == null) { + n1.left = n2.left; // そのままつける + } else if (n2.left != null) { + queue.add(new TreeNode[]{n1.left, n2.left}); // 両方あるなら後で処理 + } + + if (n1.right == null) { + n1.right = n2.right; + } else if (n2.right != null) { + queue.add(new TreeNode[]{n1.right, n2.right}); + } + } + return root1; + } +} +``` +```text +いいと思うんだけどこの関数を使っているユーザーは驚くと思わない? +関数に与えたら root1 が完全に書き換わって帰ってくるんだよ? + +1:48Claudeが返答しました: +確かに!いい指摘ですね。 +それって副作用の問題で、mergeTrees(root1, root2)を呼んだら元のroot1が破壊されるのは呼び出し側からすると予想外ですよね。 +LeetCodeだから許容されてるけど、プロダクトコードだったら完全にアウトで、新しいノードを作って返すのが正しい設計です。 +java// 呼び出し側の気持ち +TreeNode merged = mergeTrees(root1, root2); +// root1がまだ元のままだと思ってたのに書き換わってる!😱 +なので再帰の方が: + +副作用なし +コードもシンプル + +で、素直に優れてますね。BFSで練習するのはいいけど、実務・設計観点では再帰の方が正直おすすめです👍 +``` + +- 本当か?と思って上記を別チャットでレビューさせると +```text +会話の内容について +副作用の指摘は正しいです。ただ、Claudeの返答にやや雑な点があります。 + +「BFSで練習するのはいいけど、実務・設計観点では再帰の方が正直おすすめです」 + +これは少し乱暴で、再帰はスタックオーバーフローのリスクがあるという重要な観点が抜けています。木が深い(数万ノード)場合、BFSの方が安全なこともあります。副作用なしのBFS解も書けます。 +java// 副作用なし + BFS(新規ノードを作る) +Queue queue = new ArrayDeque<>(); +TreeNode root = new TreeNode(root1.val + root2.val); +queue.add(new TreeNode[]{root, root1, root2}); // 新ノード + 両ソース +// ... + + +うん、何こいつ💢 +``` \ No newline at end of file