Try an interactive version of this dialog: Sign up at solve.it.com, click Upload, and pass this URL.
import sys
sys.path.append('.')
from leet_utils import lc_setup, get_leetcode_problem, lc_export
lc_setup("https://leetcode.com/problems/invert-binary-tree/", add_cells=True)
from IPython.display import HTML
problem = get_leetcode_problem("https://leetcode.com/problems/invert-binary-tree/")
HTML(problem["content"])
Given the root of a binary tree, invert the tree, and return its root.
Example 1:
Input: root = [4,2,7,1,3,6,9] Output: [4,7,2,9,6,3,1]
Example 2:
Input: root = [2,1,3] Output: [2,3,1]
Example 3:
Input: root = [] Output: []
Constraints:
- The number of nodes in the tree is in the range
[0, 100]. -100 <= Node.val <= 100
実装1
- アルゴリズムの選択
- 全てのノードの.leftと.rightをswapできればいい。順序はなんでもいい。
- preorderが最も自然なのでpreorderでやる。
- 実装
- recursiveでもiterativeでもいいが、preorderはいわば末尾再帰の形なのでiterativeでも容易にかける。
- iterativeでやる。
- Noneチェックをstackにappend前にするか、pop後にするか。
- どっちでもいいがコード中に分散しないpop後にやる方法で書く。
- recursiveでもiterativeでもいいが、preorderはいわば末尾再帰の形なのでiterativeでも容易にかける。
- 計算量
- Time: O(n)
- Extra Space: O(n)
- 実装よりここが悩んだ。
- Noneもappendするのでstackに最大n個入りうるのでO(n)。
- Noneをappend前に弾くとしたら、O(height)。height <= n/2より結局O(n)。
from typing import Optional
# Definition for a binary tree node.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
stack = [root]
while stack:
node = stack.pop()
if node is None:
continue
node.left, node.right = node.right, node.left
stack.append(node.left)
stack.append(node.right)
return root
🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠
実装1のコードをレビューしますね。
良い点:
- シンプルで読みやすい
- iterativeで書いているのでstack overflowの心配がない(非常に深い木の場合)
Noneチェックをpop後に一箇所でやるのは一貫性があって良い
改善の余地がある点:
-
Noneをstackに入れるのは少し無駄- 毎回2つ
appendして、次のループでNoneならcontinueする - append前にチェックすれば無駄なpush/popを減らせる:
Copied!if node.left: stack.append(node.left) if node.right: stack.append(node.right)
- ただし、計算量のオーダーは変わらないので好みの問題
- 毎回2つ
-
rootがNoneのケース- 現状でも正しく動く(
Noneをpopしてcontinueして終わる) - 明示的に最初にチェックする方が意図が明確になる場合もある:
Copied!if not root: return root
- 現状でも正しく動く(
-
再帰版の方がより簡潔
- この問題は木の深さが実用上問題にならないので、再帰の方がPythonicかも:
Copied!def invertTree(self, root): if root: root.left, root.right = self.invertTree(root.right), self.invertTree(root.left) return root
総評: 現状の実装で十分良いです。改善点は「もっと良くできる」というより「別のスタイル」という感じですね。iterativeを選んだ理由(末尾再帰的な形)もノートに書かれていて、意図が明確で良いと思います。
何か気になる点はありますか?
- LLMレビューの感想
- 特になし。
- コメント集:空
- PR
- https://github.com/ryosuketc/leetcode_grind75/pull/6
- https://github.com/Ryotaro25/leetcode_first60/pull/71
- https://github.com/huyfififi/coding-challenges/pull/6
- https://github.com/rihib/leetcode/pull/26/
- https://github.com/wf9a5m75/leetcode3/pull/13
- https://github.com/kzhra/Grind41/pull/6
- https://github.com/irohafternoon/LeetCode/pull/8
🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠
各PRの内容を確認しますね。まず全てのPRを読み込みます。
{
"id": "toolu_01VfAU6M82oCktUnV27seeTd",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/ryosuketc/leetcode_grind75/pull/6"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_011rK6csHMucvniH479xKpti",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/Ryotaro25/leetcode_first60/pull/71"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_01F66B3B5DFY9WggCDSYbTMU",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/huyfififi/coding-challenges/pull/6"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_01Ps1CifJoYJeGmfsx9TsJE5",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/rihib/leetcode/pull/26"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_012wNH7tsjCAQy4YpbYqdKMd",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/wf9a5m75/leetcode3/pull/13"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_017hoaZzhyTvU3ua6QS3jdzi",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/kzhra/Grind41/pull/6"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_01TvDZpQhZnjxhoUZWEgqR72",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/irohafternoon/LeetCode/pull/8"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
各PRの内容をまとめます:
1. ryosuketc/leetcode_grind75 PR#6 (C++)
- 実装: C++でiterativeとrecursive両方
- コメント:
std::moveとGCについての議論 → C++にはGCがない。スタックの自動解放とGCは別物nullptrvsNULL→nullptr一択(NULLは問題を起こすことがある)- 日本語個人ブログは信頼性に注意、cpprefjp等の公式リファレンスで裏取りを推奨
2. Ryotaro25/leetcode_first60 PR#71 (C++)
- 実装: 再帰版とBFS版
- コメント:
std::swapを使ってswap→invertの順でも良い- root == nullの早期チェックは好みの問題(統一感の議論)
using namespace std;を避け、std::queueのように明示する方が実務的
3. huyfififi/coding-challenges PR#6 (Python)
- 実装: DFS(stack)とBFS(queue)
- コメント:
- stackに
Noneを入れるか入れないかは好みの範囲 - ただしBFSでvisitedフラグを出てきた後に立てると急激に遅くなるケースがある
a, b = b, aはPythonのイディオムとして積極的に使ってよい
- stackに
4. rihib/leetcode PR#26 (Go)
- 実装: 再帰版
- コメント: odaさんからレビューがあったが詳細は見えず
- ラベル: Grind75, Blind75, NeetCode150, Easy, Recursion, Stack
5. wf9a5m75/leetcode3 PR#13 (Python)
- 実装: 再帰版とiterativeの複数パターン
- コメント:
- 不要な条件分岐(
if right_tree:等)は消せる - stackに
Noneを入れて出てきたらチェック、でもOK - ただし
Noneを入れないことでループ回数が減る(僅かだが)ので好み - 「どちらでもいい」との結論
- 不要な条件分岐(
6. kzhra/Grind41 PR#6 (C++)
- 実装: BFS(queue)を中心に複数パターン
- コメント:
- 不要なelse節を削除可能
if (root->left)のような条件なくても動く- queueに全部入れてpop後にnullptrチェックする手もある
- nullptrなら早めに抜けてネストを浅くする提案
7. irohafternoon/LeetCode PR#8 (C++)
- 注意: このPRは「Valid Parentheses」の問題で、Invert Binary Treeではありません
- コメント(参考として):
何か特定のPRについて詳しく知りたい点はありますか?
🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠
各トピックを深掘りしますね。
1. C++のnullptr vs NULL
NULLは歴史的に0または(void*)0として定義されており、整数としても解釈されうる問題があります:
void f(int);
void f(char*);
f(NULL); // どっちが呼ばれる?→ f(int)が呼ばれてしまうことがある
f(nullptr); // 確実にf(char*)が呼ばれる
nullptrはC++11で導入されたstd::nullptr_t型で、ポインタ型にのみ変換可能。オーバーロード解決で曖昧さが生じません。
2. std::moveとGCの話
C++にはGarbage Collectorがありません。メモリ管理は:
std::moveは「所有権の移動」を示すキャストで、実際にはムーブコンストラクタ/代入演算子を呼び出すためのヒント:
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // v1の中身がv2に移動、v1は空になる
これはGCとは無関係で、コピーを避けてパフォーマンスを上げる仕組みです。
3. using namespace std;を避ける理由
using namespace std; // 名前空間を汚染
問題点:
実務では:
std::vector<int> v; // 明示的に書く
std::cout << "hello"; // どこから来たか一目瞭然
競プロでは時間短縮のために使うこともありますが、仕事のコードでは避けるのが一般的です。
4. std::swap vs 手動swap
// 方法1: std::swap
std::swap(node->left, node->right);
// 方法2: 手動
auto temp = node->left;
node->left = node->right;
node->right = temp;
std::swapのメリット:
- 読みやすい、意図が明確
- 型によっては最適化されたswapが呼ばれる(ムーブセマンティクス対応)
Pythonではa, b = b, aが言語組み込みのイディオムなのでそちらを使います。
5. stackにNone/nullptrを入れるかどうか
入れる派(pop後チェック):
stack.append(node.left) # Noneでも入れる
stack.append(node.right)
# pop後に
if node is None: continue
- コードがシンプル、append箇所でのチェック不要
- ループ回数は増える(無駄なpush/pop)
入れない派(append前チェック):
if node.left: stack.append(node.left)
if node.right: stack.append(node.right)
- ループ回数が減る
- チェックが分散する
計算量のオーダーは同じ(O(n))なので、好みの問題という結論が多いです。
6. BFSでvisitedフラグを「出てきた後」に立てる問題
グラフ探索でのBFS:
# ❌ 遅い:出てきた後にvisited
while queue:
node = queue.popleft()
if node in visited: continue
visited.add(node) # ここで追加
for neighbor in node.neighbors:
queue.append(neighbor)
問題:同じノードがqueueに何度も入る可能性がある(特に密なグラフで爆発)
# ✅ 速い:入れる前にvisited
while queue:
node = queue.popleft()
for neighbor in node.neighbors:
if neighbor not in visited:
visited.add(neighbor) # 入れる前に追加
queue.append(neighbor)
木ではサイクルがないのでこの問題は起きませんが、一般のグラフでは重要です。
7. emplace vs push
std::stack<TreeNode*> s;
s.push(node); // nodeをコピー/ムーブしてスタックに入れる
s.emplace(node); // in-placeで構築(コンストラクタ引数を直接渡せる)
ポインタの場合は差がほぼありませんが、オブジェクトの場合:
std::vector<std::pair<int, int>> v;
v.push_back(std::make_pair(1, 2)); // pairを作ってからコピー
v.emplace_back(1, 2); // 直接構築(効率的)
std::unordered_set<int> s = {1, 2, 3};
// C++17以前
if (s.count(2)) { ... } // 0か1を返す(setの場合)
// C++20以降
if (s.contains(2)) { ... } // boolを返す、意図が明確
containsの方が「存在確認」という意図が明確で読みやすいです。
特に気になるトピックがあれば、さらに詳しく説明しますよ!
class Solution:
def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if root is None:
return None
stack = [root]
while stack:
node = stack.pop()
node.left, node.right = node.right, node.left
if node.left is not None:
stack.append(node.left)
if node.right is not None:
stack.append(node.right)
return root
lc_export(
next_url="https://leetcode.com/problems/valid-anagram",
dry_run=True,
desc="",
notebook_url="https://share.solve.it.com/d/9bd3c55307a8d0799a9d6b2fdeb50133"
)
=== DRY RUN: 0226-invert-binary-tree/0226-invert-binary-tree.md ===
# 226. Invert Binary Tree
- URL: https://leetcode.com/problems/invert-binary-tree/
- Difficulty: Easy
- Tags: Tree, Depth-First Search, Breadth-First Search, Binary Tree
- Notebook: https://share.solve.it.com/d/9bd3c55307a8d0799a9d6b2fdeb50133
## 問題文
<p>Given the <code>root</code> of a binary tree, invert the tree, and return <em>its root</em>.</p>
<p> </p>
<p><strong class="example">Example 1:</strong></p>
<img alt="" src="https://assets.leetcode.com/uploads/2021/03/14/invert1-tree.jpg" style="width: 500px; height: 165px;" />
<pre>
<strong>Input:</strong> root = [4,2,7,1,3,6,9]
<strong>Output:</strong> [4,7,2,9,6,3,1]
</pre>
<p><strong class="example">Example 2:</strong></p>
<img alt="" src="https://assets.leetcode.com/uploads/2021/03/14/invert2-tree.jpg" style="width: 500px; height: 120px;" />
<pre>
<strong>Input:</strong> root = [2,1,3]
<strong>Output:</strong> [2,3,1]
</pre>
<p><strong class="example">Example 3:</strong></p>
<pre>
<strong>Input:</strong> root = []
<stro...
=== 17 cells would be exported ===
=== PR Title: 226. Invert Binary Tree ===
=== Notebook: https://share.solve.it.com/d/9bd3c55307a8d0799a9d6b2fdeb50133 ===
'# 226. Invert Binary Tree\n\n- URL: https://leetcode.com/problems/invert-binary-tree/\n- Difficulty: Easy\n- Tags: Tree, Depth-First Search, Breadth-First Search, Binary Tree\n- Notebook: https://share.solve.it.com/d/9bd3c55307a8d0799a9d6b2fdeb50133\n\n## 問題文\n\n<p>Given the <code>root</code> of a binary tree, invert the tree, and return <em>its root</em>.</p>\n\n<p> </p>\n<p><strong class="example">Example 1:</strong></p>\n<img alt="" src="https://assets.leetcode.com/uploads/2021/03/14/invert1-tree.jpg" style="width: 500px; height: 165px;" />\n<pre>\n<strong>Input:</strong> root = [4,2,7,1,3,6,9]\n<strong>Output:</strong> [4,7,2,9,6,3,1]\n</pre>\n\n<p><strong class="example">Example 2:</strong></p>\n<img alt="" src="https://assets.leetcode.com/uploads/2021/03/14/invert2-tree.jpg" style="width: 500px; height: 120px;" />\n<pre>\n<strong>Input:</strong> root = [2,1,3]\n<strong>Output:</strong> [2,3,1]\n</pre>\n\n<p><strong class="example">Example 3:</strong></p>\n\n<pre>\n<strong>Input:</strong> root = []\n<strong>Output:</strong> []\n</pre>\n\n<p> </p>\n<p><strong>Constraints:</strong></p>\n\n<ul>\n\t<li>The number of nodes in the tree is in the range <code>[0, 100]</code>.</li>\n\t<li><code>-100 <= Node.val <= 100</code></li>\n</ul>\n\n\n## Step 1\n\n### 実装1\n\n- アルゴリズムの選択\n - 全てのノードの.leftと.rightをswapできればいい。順序はなんでもいい。\n - preorderが最も自然なのでpreorderでやる。\n- 実装\n - recursiveでもiterativeでもいいが、preorderはいわば末尾再帰の形なのでiterativeでも容易にかける。\n - iterativeでやる。\n - Noneチェックをstackにappend前にするか、pop後にするか。\n - どっちでもいいがコード中に分散しないpop後にやる方法で書く。\n- 計算量\n - Time: O(n)\n - Extra Space: O(n)\n - 実装よりここが悩んだ。\n - Noneもappendするのでstackに最大n個入りうるのでO(n)。\n - Noneをappend前に弾くとしたら、O(height)。height <= n/2より結局O(n)。\n\n```python\nfrom typing import Optional\n\n\n# Definition for a binary tree node.\nclass TreeNode:\n def __init__(self, val=0, left=None, right=None):\n self.val = val\n self.left = left\n self.right = right\n\n\nclass Solution:\n def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:\n stack = [root]\n while stack:\n node = stack.pop()\n if node is None:\n continue\n node.left, node.right = node.right, node.left\n stack.append(node.left)\n stack.append(node.right)\n return root\n \n```\n\n- 3分かかった。\n- 振り返り\n - 書いている間、特に何も悩まなかった。\n\n## Step 2\n\n- LLMレビューの感想\n - 特になし。\n- コメント集:空\n- PR\n - https://github.com/ryosuketc/leetcode_grind75/pull/6\n - https://github.com/Ryotaro25/leetcode_first60/pull/71\n - https://github.com/huyfififi/coding-challenges/pull/6\n - https://github.com/rihib/leetcode/pull/26/\n - https://github.com/wf9a5m75/leetcode3/pull/13\n - https://github.com/kzhra/Grind41/pull/6\n - https://github.com/irohafternoon/LeetCode/pull/8\n\n**Q:** 上記に他の人が解いたPRをまとめています。それぞれのPRの学習記録とそれについているコメントをあなたがチェックし、PRごとの内容を簡潔にまとめて欲しいです。\n\n\n**A:**\n各PRの内容を確認しますね。まず全てのPRを読み込みます。\n\n各PRの内容をまとめます:\n\n---\n\n**1. ryosuketc/leetcode_grind75 PR#6** (C++)\n- **実装**: C++でiterativeとrecursive両方\n- **コメント**:\n - `std::move`とGCについての議論 → C++にはGCがない。スタックの自動解放とGCは別物\n - `nullptr` vs `NULL` → `nullptr`一択(`NULL`は問題を起こすことがある)\n - 日本語個人ブログは信頼性に注意、cpprefjp等の公式リファレンスで裏取りを推奨\n\n---\n\n**2. Ryotaro25/leetcode_first60 PR#71** (C++)\n- **実装**: 再帰版とBFS版\n- **コメント**:\n - `std::swap`を使ってswap→invertの順でも良い\n - root == nullの早期チェックは好みの問題(統一感の議論)\n - `using namespace std;`を避け、`std::queue`のように明示する方が実務的\n\n---\n\n**3. huyfififi/coding-challenges PR#6** (Python)\n- **実装**: DFS(stack)とBFS(queue)\n- **コメント**:\n - stackに`None`を入れるか入れないかは好みの範囲\n - ただしBFSでvisitedフラグを出てきた後に立てると急激に遅くなるケースがある\n - `a, b = b, a`はPythonのイディオムとして積極的に使ってよい\n\n---\n\n**4. rihib/leetcode PR#26** (Go)\n- **実装**: 再帰版\n- **コメント**: odaさんからレビューがあったが詳細は見えず\n- ラベル: Grind75, Blind75, NeetCode150, Easy, Recursion, Stack\n\n---\n\n**5. wf9a5m75/leetcode3 PR#13** (Python)\n- **実装**: 再帰版とiterativeの複数パターン\n- **コメント**:\n - 不要な条件分岐(`if right_tree:`等)は消せる\n - stackに`None`を入れて出てきたらチェック、でもOK\n - ただし`None`を入れないことでループ回数が減る(僅かだが)ので好み\n - 「どちらでもいい」との結論\n\n---\n\n**6. kzhra/Grind41 PR#6** (C++)\n- **実装**: BFS(queue)を中心に複数パターン\n- **コメント**:\n - 不要なelse節を削除可能\n - `if (root->left)`のような条件なくても動く\n - queueに全部入れてpop後にnullptrチェックする手もある\n - nullptrなら早めに抜けてネストを浅くする提案\n\n---\n\n**7. irohafternoon/LeetCode PR#8** (C++)\n- **注意**: このPRは「Valid Parentheses」の問題で、Invert Binary Treeではありません\n- **コメント**(参考として):\n - `using namespace std;`は名前空間を汚すので好まれない\n - `map`に`const`修飾を付けると`[]`ではなく`at`でアクセスが必要\n - `count`より`contains`(C++20)が明示的\n - `emplace` vs `push`の違い(in-place構築 vs コピー/移動)\n\n---\n\n何か特定のPRについて詳しく知りたい点はありますか?\n\n- > ただしBFSでvisitedフラグを出てきた後に立てると急激に遅くなるケースがある\n - これは痛い目を見たことあるので、必ず入れる前にチェックする癖がついている。\n - グラフ系だと入れる前にチェックしてもコードの重複が起きないので心理的に書きやすい。\n ```py\n while frontiers:\n node = frontiers.popleft()\n for neighbor in node.neighbors:\n if neighbor not in visited:\n visited.add(neighbor)\n frontiers.append(neighbor)\n ```\n\n### 実装2\n\n- 一応、append前にNoneチェックの方法。\n- append_if_exist()みたいなヘルパー作る気も進まないのでそのまま書く。\n\n```python\nclass Solution:\n def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:\n if root is None:\n return None\n \n stack = [root]\n while stack:\n node = stack.pop()\n node.left, node.right = node.right, node.left\n if node.left is not None:\n stack.append(node.left)\n if node.right is not None:\n stack.append(node.right)\n return root\n```\n\n## Step 3\n\n[実装1](#実装1)\n\n## Step 4\n\n自分のPR:(自分のPRへのリンク)\n\n**Q:** それぞれのレビューコメントについて、もう少し詳しく教えてください。\n\n### 実装3\n\n- レビューを受けて書き直し\n - (修正点)\n\n```python\nclass Solution:\n pass\n```'