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/valid-palindrome/", add_cells=False)
from IPython.display import HTML
problem = get_leetcode_problem("https://leetcode.com/problems/valid-palindrome/")
HTML(problem["content"])
A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.
Given a string s, return true if it is a palindrome, or false otherwise.
Example 1:
Input: s = "A man, a plan, a canal: Panama" Output: true Explanation: "amanaplanacanalpanama" is a palindrome.
Example 2:
Input: s = "race a car" Output: false Explanation: "raceacar" is not a palindrome.
Example 3:
Input: s = " " Output: true Explanation: s is an empty string "" after removing non-alphanumeric characters. Since an empty string reads the same forward and backward, it is a palindrome.
Constraints:
1 <= s.length <= 2 * 105sconsists only of printable ASCII characters.
実装1
- アルゴリズムの選択
- 大まかには入力をパース+正規化し、両端から整合を見ていく方針。
- パース+正規化した中間結果を変数としておくとExtra Spaceを食うが、その方がシンプルコードになりそう。
- 一方で、パース+正規化を逐次的に行うことでExtra Space: O(1)で書くこともできる。
- 関数型っぽい書き方をうまく実装すればシンプルさを維持できるかもしれないが、思いつかない。
- map、filterなどを使うのだろうか。
- 一旦は前者の中間結果を変数として置く書き方で書いてみる。
- 実装
- reモジュールで必要な文字をマッチする方法が思いついた。
- その後、alphanumericな文字に対し、逐次的にstr.lower()を使う。
- 計算量
- Time: O(n)
- Extra Space: O(n)
import re
class Solution:
def isPalindrome(self, s: str) -> bool:
alphanumerics = re.findall(r"[a-zA-Z0-9]", s)
normalized = [ch.lower() for ch in alphanumerics]
# read bidirectionally
left = 0
right = len(normalized) - 1
while left < right:
if normalized[left] != normalized[right]:
return False
left += 1
right -= 1
return True
class Solution:
def isPalindrome(self, s: str) -> bool:
def skip_non_alphanumeric(index, step):
while 0 <= index < len(s):
if s[index].isalpha() or s[index].isdecimal():
break
index += step
return index
# read bidirectionally
left = skip_non_alphanumeric(0, 1)
right = skip_non_alphanumeric(len(s) - 1, -1)
while left < right:
if s[left].lower() != s[right].lower():
return False
left = skip_non_alphanumeric(left + 1, 1)
right = skip_non_alphanumeric(right - 1, -1)
return True
🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠
両方の実装をレビューしますね!
実装1について
良い点:
- シンプルで読みやすい
- 正規表現の使い方は正しい
改善点:
- 振り返りで書かれている通り、
s.lower()を先にやる方が効率的 - 両端比較は
reversedやスライスで簡潔に書ける:Copied!return normalized == normalized[::-1]
str.isalnum()で十分では?(後述)
実装2について
良い点:
- O(1) Extra Spaceを達成
- ヘルパー関数で意図が明確
改善点:
isalpha() or isdecimal()→isalnum()で良いと思います- 問題文は "alphanumeric" としか言っておらず、ASCII範囲外の文字がテストケースに含まれる可能性は低い
- 仮にあっても、Unicodeのalphanumericを受け入れる方が自然な解釈とも言える
- ヘルパー関数名:
skip_non_alphanumericは動作を表しているが、find_next_alnumのような「何を返すか」を示す名前も検討の余地あり - ループ内で毎回
len(s)を呼んでいる(微小だがキャッシュ可能)
その他の書き方案
実装1の簡潔版:
def isPalindrome(self, s: str) -> bool:
cleaned = [c for c in s.lower() if c.isalnum()]
return cleaned == cleaned[::-1]
実装2のジェネレータ版(関数型っぽく):
def isPalindrome(self, s: str) -> bool:
def chars():
for c in s.lower():
if c.isalnum():
yield c
forward = list(chars())
return forward == forward[::-1]
ただし、O(1) Extra Spaceを維持しつつ関数型っぽく書くのは難しいですね。実装2のアプローチが素直だと思います。
何か気になる点はありますか?
- LLMレビューの感想
- return normalized == normalized[::-1]は簡潔だがスライスで重くなるのでなんとも言えない。
- まぁ確かにstr.isalnum()で十分かもしれない。厳密にアラビア数字だけにしたいなら正規表現でいいので。
- ヘルパー関数名、確かにind_next_alnumの方が直感的で情報量も多い気がする。
-
O(1) Extra Spaceを維持しつつ関数型っぽく書くのは難しい
- なるほど。コメント集などもみて適宜検討。
- コメント集
- https://discord.com/channels/1084280443945353267/1192728121644945439/1218815342206386176
-
本質的にはそれほど変わりません。...これ、3つのループにもできますし、1つのループにもできます。
- その通りで、いろいろ脳内で試して可読性、速度、メモリのトレードオフを見定める感覚。
-
- https://discord.com/channels/1084280443945353267/1253694251271852095/1281522459753058357
-
Go 言語っぽい書き方とか探してみませんか
- Go言語のgoroutineとchannelを使って、並列処理で書く。
- goroutine (
go func()) = 軽量スレッドのようなもの。go filter(...)で別の処理が並列に走り出す - channel (
chan rune) = スレッド間でデータをやり取りするパイプ。PythonのQueueに近いc <- value: channelに値を送るvalue := <- c: channelから値を受け取る(届くまで待つ)
- 左から読むgoroutineと右から読むgoroutineが同時に動き、それぞれがchannelに1文字ずつ送る。メインのループで両方から1文字ずつ受け取って比較する
- goroutine (
-
- https://discord.com/channels/1084280443945353267/1192728121644945439/1218815342206386176
- PR
- https://github.com/Kitaken0107/GrindEasy/pull/8
- https://github.com/rihib/leetcode/pull/25
- https://github.com/ryosuketc/leetcode_grind75/pull/5
- https://github.com/huyfififi/coding-challenges/pull/5
- https://github.com/kzhra/Grind41/pull/5
- https://github.com/t-ooka/leetcode/pull/6
🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠
各PRの内容を確認しますね。
{
"id": "toolu_01DFtZwrsvhNECxY81F5BF9z",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/Kitaken0107/GrindEasy/pull/8"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_01CUsBpFtYisepgwNEeCtV4K",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/rihib/leetcode/pull/25"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_01WT78ZTT7mF44oeBvzd3gPU",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/ryosuketc/leetcode_grind75/pull/5"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_01J1yVwy2ac5dNmFvMwArMg4",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/huyfififi/coding-challenges/pull/5"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_01Vvj4M2xzr2TGTBC9ev9ofA",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/kzhra/Grind41/pull/5"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
{
"id": "toolu_011Be1uxnAhJGxcQtc1Sxv8J",
"call": {
"function": "read_url",
"arguments": {
"url": "https://github.com/t-ooka/leetcode/pull/6"
}
},
"result": "'Skip to content\\n\\n## Navigation Menu\\n\\nToggle navigation\\n\\n[ ](/)\\n\\n[ Sign in ](/login?return_t<TRUNCATED>"
}
各PRの内容を確認しました。以下にまとめます:
1. Kitaken0107/GrindEasy#8 (Python)
主なコメント:
2. rihib/leetcode#25 (Go)
主なコメント:
- goroutineとchannelを使った並列処理の実装にラベル付け
- 具体的なコードへのコメントは少なめ
3. ryosuketc/leetcode_grind75#5 (C++)
主なコメント(非常に充実):
- C++では文字列はmutable、Pythonの
+=での文字列コピーとは異なる - 関数の引数は
const string&かstring_viewで渡すべき(コピー回避) - 戻り値のコピーを避けるには引数に参照を渡す方法もある(ただしNRVOで最適化される可能性あり)
reserveで事前にcapacityを確保する選択肢- メモリコピーの実行時間見積もり(1 clock で4-8バイト程度)
- Google Style Guideでは
publicを先に宣言 - Rope、Piece Tableなどのデータ構造の紹介
4. huyfififi/coding-challenges#5 (Python)
主なコメント:
str.isalnum()のドキュメントを確認すべきisalnum()は"四"や"²"などUnicodeの数字もTrueを返すため、問題の意図と異なる可能性がある → 面接では確認が必要
5. kzhra/Grind41#5 (C++)
主なコメント:
elseを省いてインデントを浅くする==のときはループを抜けて良い- Google Style Guide準拠の空白の入れ方(キーワード後、
{前、:前後など) std::tolower()とisalnum()を組み合わせてif文を簡潔にcontinueの一貫性、不要なcontinueは削除- early returnで条件を反転した方が読みやすい
- 変数名は
left/rightの方が直感的
6. t-ooka/leetcode#6 (Python/C++)
主なコメント:
i/jよりleft/rightの方が分かりやすい!= Trueよりnot Xの方が自然- whileが連続すると読みにくい →
if not ... : continueパターンの提案 - PEP8ではwhileと同じ行に実行文を置くことは非推奨
共通して多かった指摘:
何か気になるPRがあれば、詳しく見ていきましょうか?
🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠🧠
各トピックを深掘りしていきますね。
1. isalnum()のUnicode問題
Pythonのstr.isalnum()はUnicode全体を対象にするため、ASCII以外もTrueを返します:
"四".isalnum() # True (漢数字)
"²".isalnum() # True (上付き数字)
"α".isalnum() # True (ギリシャ文字)
"A".isalnum() # True
問題文の"alphanumeric"がASCIIのみを意図している場合、これは意図と異なる動作になります。
厳密にASCIIのみにしたい場合:
c.isascii() and c.isalnum()
# または
c in string.ascii_letters + string.digits
# または
re.match(r'[a-zA-Z0-9]', c)
面接では「alphanumericとはASCIIのみですか?」と確認するのがベスト。
2. C++の文字列操作とメモリ効率
Pythonでは文字列はimmutableなので s += c は毎回新しい文字列を作成(O(n))。一方、C++のstd::stringはmutableで、+=は末尾追加(償却O(1))。
// C++: 効率的
std::string result;
result.reserve(s.size()); // 事前にメモリ確保
for (char c : s) {
if (std::isalnum(c)) result += std::tolower(c);
}
reserve()で事前にcapacityを確保すると、再割り当てを防げます。
関数の引数での文字列の渡し方:
// コピーが発生(遅い)
bool isPalindrome(string s)
// 参照渡し(コピーなし、推奨)
bool isPalindrome(const string& s)
// C++17以降、読み取り専用ならこれも良い
bool isPalindrome(string_view s)
3. early returnとインデント削減
深いネストは読みにくい:
def process(x):
if condition1:
if condition2:
if condition3:
# 本処理
early returnで平坦に:
def process(x):
if not condition1:
return
if not condition2:
return
if not condition3:
return
# 本処理
同様に、ループ内ではcontinueで早期スキップ:
while left < right:
if not s[left].isalnum():
left += 1
continue
if not s[right].isalnum():
right -= 1
continue
# 本処理
i/jは汎用的すぎて意図が伝わりにくい。この問題では両端から走査しているので、left/rightの方が:
- 走査方向が明確
- コードを読んだだけで動作がイメージできる
- バグ発見が容易(
leftが増えるべき場所で減っていたら気づきやすい)
5. Goのgoroutine/channel並列処理
func isPalindrome(s string) bool {
leftCh := make(chan rune)
rightCh := make(chan rune)
// 左から読むgoroutine
go func() {
for _, c := range s {
if isAlnum(c) {
leftCh <- unicode.ToLower(c)
}
}
close(leftCh)
}()
// 右から読むgoroutine
go func() {
runes := []rune(s)
for i := len(runes) - 1; i >= 0; i-- {
if isAlnum(runes[i]) {
rightCh <- unicode.ToLower(runes[i])
}
}
close(rightCh)
}()
// メインで比較
for l := range leftCh {
r := <-rightCh
if l != r {
return false
}
}
return true
}
この書き方の利点:
- 左右の走査が独立して動く
- channelで同期するので、必要な分だけ処理が進む
- 概念的にきれい(ただし、この問題ではオーバーキル)
6. Rope / Piece Table データ構造
長い文字列の編集が頻繁にある場合(エディタなど)に使われる:
- Rope: 文字列を二分木で管理。部分文字列の挿入・削除がO(log n)
- Piece Table: 元テキスト+追加バッファへの参照(オフセット, 長さ)のリストで管理。VS Codeが採用
この問題では不要ですが、「文字列操作の効率」という文脈で言及されています。
どのトピックについてさらに詳しく知りたいですか?
- そのほか、コードを見た感想
- https://github.com/huyfififi/coding-challenges/pull/5
- step1_bの方法はシンプルで良いと思った。-> 実装3
- https://github.com/huyfififi/coding-challenges/pull/5
実装4
- 何も見ずに書き直し
- 可読性の高さから実装1ベースの実装を選択。
- alphanumericの判定には正規表現が必要十分で最適と判断。
lc_export(
next_url="https://leetcode.com/problems/invert-binary-tree/",
dry_run=False,
desc="",
notebook_url="https://share.solve.it.com/d/e80e6c41b8c79d6a8e2915069f91e325"
)
⚠ Branch 0125-valid-palindrome already exists, updating...
✓ Update 0125-valid-palindrome: 0125-valid-palindrome/0125-valid-palindrome.md
⚠ PR already exists for 0125-valid-palindrome
'# 125. Valid Palindrome\n\n- URL: https://leetcode.com/problems/valid-palindrome/\n- Difficulty: Easy\n- Tags: Two Pointers, String\n- Notebook: https://share.solve.it.com/d/e80e6c41b8c79d6a8e2915069f91e325\n\n## 問題文\n\n<p>A phrase is a <strong>palindrome</strong> if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.</p>\n\n<p>Given a string <code>s</code>, return <code>true</code><em> if it is a <strong>palindrome</strong>, or </em><code>false</code><em> otherwise</em>.</p>\n\n<p> </p>\n<p><strong class="example">Example 1:</strong></p>\n\n<pre>\n<strong>Input:</strong> s = "A man, a plan, a canal: Panama"\n<strong>Output:</strong> true\n<strong>Explanation:</strong> "amanaplanacanalpanama" is a palindrome.\n</pre>\n\n<p><strong class="example">Example 2:</strong></p>\n\n<pre>\n<strong>Input:</strong> s = "race a car"\n<strong>Output:</strong> false\n<strong>Explanation:</strong> "raceacar" is not a palindrome.\n</pre>\n\n<p><strong class="example">Example 3:</strong></p>\n\n<pre>\n<strong>Input:</strong> s = " "\n<strong>Output:</strong> true\n<strong>Explanation:</strong> s is an empty string "" after removing non-alphanumeric characters.\nSince an empty string reads the same forward and backward, it is a palindrome.\n</pre>\n\n<p> </p>\n<p><strong>Constraints:</strong></p>\n\n<ul>\n\t<li><code>1 <= s.length <= 2 * 10<sup>5</sup></code></li>\n\t<li><code>s</code> consists only of printable ASCII characters.</li>\n</ul>\n\n\n## Step 1\n\n### 実装1\n\n- アルゴリズムの選択\n - 大まかには入力をパース+正規化し、両端から整合を見ていく方針。\n - パース+正規化した中間結果を変数としておくとExtra Spaceを食うが、その方がシンプルコードになりそう。\n - 一方で、パース+正規化を逐次的に行うことでExtra Space: O(1)で書くこともできる。\n - 関数型っぽい書き方をうまく実装すればシンプルさを維持できるかもしれないが、思いつかない。\n - map、filterなどを使うのだろうか。\n - 一旦は前者の中間結果を変数として置く書き方で書いてみる。\n- 実装\n - reモジュールで必要な文字をマッチする方法が思いついた。\n - その後、alphanumericな文字に対し、逐次的にstr.lower()を使う。\n- 計算量\n - Time: O(n)\n - Extra Space: O(n)\n\n```python\nimport re\n\n\nclass Solution:\n def isPalindrome(self, s: str) -> bool:\n alphanumerics = re.findall(r"[a-zA-Z0-9]", s)\n normalized = [ch.lower() for ch in alphanumerics]\n\n # read bidirectionally\n left = 0\n right = len(normalized) - 1\n while left < right:\n if normalized[left] != normalized[right]:\n return False\n left += 1\n right -= 1\n return True\n```\n\n- 10分ほどかかった。\n- 振り返り\n - ch.lower()は最初に全体まとめてやった方が速そう。\n ```py\n normalized = re.findall(r"[a-zA-Z0-9]", s.lower())\n ```\n - 今の実装は明示的に内包表記でループしているのでstr.lower()のネイティブコードの恩恵を受けられない。\n - 次の実装はExtra Space: O(1)で、なるべくシンプルに書きたい。\n - re.findall()以外の方法もありそう。str型のメソッドやstringモジュールなど、探せば見つかりそう。\n\n### 実装2\n\n- アルゴリズムと実装\n - Extra Space: O(1)で書いてみる。\n - reモジュールに頼らず書いてみる。\n- 計算量\n - Time: O(n)\n - Extra Space: O(1)\n\n```python\nclass Solution:\n def isPalindrome(self, s: str) -> bool:\n def skip_non_alphanumeric(index, step):\n while 0 <= index < len(s):\n if s[index].isalpha() or s[index].isdecimal():\n break\n index += step\n return index\n \n # read bidirectionally\n left = skip_non_alphanumeric(0, 1)\n right = skip_non_alphanumeric(len(s) - 1, -1)\n while left < right:\n if s[left].lower() != s[right].lower():\n return False\n left = skip_non_alphanumeric(left + 1, 1)\n right = skip_non_alphanumeric(right - 1, -1)\n return True\n```\n\n- 振り返り\n - .isalpha() or .isdecimal() のところは、一応ASCII以外が来てもある程度厳密にチェックできるように.isalnum()を避けてみた。\n - ただそれをいうなら本当に厳密なのはre.match(r"[a-zA-Z0-9]", s[index])な気がする。\n\n## Step 2\n\n**Q:** 実装1、2のコードをレビューして、改善点を教えてください。\n\n\n**A:**\n両方の実装をレビューしますね!\n\n**実装1について**\n\n良い点:\n- シンプルで読みやすい\n- 正規表現の使い方は正しい\n\n改善点:\n1. 振り返りで書かれている通り、`s.lower()`を先にやる方が効率的\n2. 両端比較は`reversed`やスライスで簡潔に書ける:\n ```python\n return normalized == normalized[::-1]\n ```\n3. `str.isalnum()`で十分では?(後述)\n\n**実装2について**\n\n良い点:\n- O(1) Extra Spaceを達成\n- ヘルパー関数で意図が明確\n\n改善点:\n1. `isalpha() or isdecimal()` → `isalnum()`で良いと思います\n - 問題文は "alphanumeric" としか言っておらず、ASCII範囲外の文字がテストケースに含まれる可能性は低い\n - 仮にあっても、Unicodeのalphanumericを受け入れる方が自然な解釈とも言える\n2. ヘルパー関数名:`skip_non_alphanumeric`は動作を表しているが、`find_next_alnum`のような「何を返すか」を示す名前も検討の余地あり\n3. ループ内で毎回`len(s)`を呼んでいる(微小だがキャッシュ可能)\n\n**その他の書き方案**\n\n実装1の簡潔版:\n```python\ndef isPalindrome(self, s: str) -> bool:\n cleaned = [c for c in s.lower() if c.isalnum()]\n return cleaned == cleaned[::-1]\n```\n\n実装2のジェネレータ版(関数型っぽく):\n```python\ndef isPalindrome(self, s: str) -> bool:\n def chars():\n for c in s.lower():\n if c.isalnum():\n yield c\n forward = list(chars())\n return forward == forward[::-1]\n```\n\nただし、O(1) Extra Spaceを維持しつつ関数型っぽく書くのは難しいですね。実装2のアプローチが素直だと思います。\n\n何か気になる点はありますか?\n\n- LLMレビューの感想\n - return normalized == normalized[::-1]は簡潔だがスライスで重くなるのでなんとも言えない。\n - まぁ確かにstr.isalnum()で十分かもしれない。厳密にアラビア数字だけにしたいなら正規表現でいいので。\n - ヘルパー関数名、確かにind_next_alnumの方が直感的で情報量も多い気がする。\n - > O(1) Extra Spaceを維持しつつ関数型っぽく書くのは難しい\n - なるほど。コメント集などもみて適宜検討。\n- [コメント集](https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.idizyy9b2j62)\n - https://discord.com/channels/1084280443945353267/1192728121644945439/1218815342206386176\n - > 本質的にはそれほど変わりません。...これ、3つのループにもできますし、1つのループにもできます。\n - その通りで、いろいろ脳内で試して可読性、速度、メモリのトレードオフを見定める感覚。\n - https://discord.com/channels/1084280443945353267/1253694251271852095/1281522459753058357\n - > Go 言語っぽい書き方とか探してみませんか\n - Go言語のgoroutineとchannelを使って、並列処理で書く。\n 1. goroutine (`go func()`) = 軽量スレッドのようなもの。`go filter(...)`で別の処理が並列に走り出す\n 2. channel (`chan rune`) = スレッド間でデータをやり取りするパイプ。Pythonの`Queue`に近い\n - `c <- value` : channelに値を送る\n - `value := <- c` : channelから値を受け取る(届くまで待つ)\n 3. 左から読むgoroutineと右から読むgoroutineが同時に動き、それぞれがchannelに1文字ずつ送る。メインのループで両方から1文字ずつ受け取って比較する\n- PR\n - https://github.com/Kitaken0107/GrindEasy/pull/8\n - https://github.com/rihib/leetcode/pull/25\n - https://github.com/ryosuketc/leetcode_grind75/pull/5\n - https://github.com/huyfififi/coding-challenges/pull/5\n - https://github.com/kzhra/Grind41/pull/5\n - https://github.com/t-ooka/leetcode/pull/6\n\n**Q:** 上記に他の人が解いたPRをまとめています。それぞれのPRの学習記録とそれについているコメントをあなたがチェックし、PRごとの内容を簡潔にまとめて欲しいです。\n\n\n**A:**\n各PRの内容を確認しますね。\n\n各PRの内容を確認しました。以下にまとめます:\n\n---\n\n**1. Kitaken0107/GrindEasy#8** (Python)\n\n主なコメント:\n- 数字も回文判定に含めるべきだが考慮されていない指摘\n- `left`/`right`ポインタで`while left < right`とする書き方の方が分かりやすい\n- 長さ1以下の場合のif文は不要\n\n---\n\n**2. rihib/leetcode#25** (Go)\n\n主なコメント:\n- goroutineとchannelを使った並列処理の実装にラベル付け\n- 具体的なコードへのコメントは少なめ\n\n---\n\n**3. ryosuketc/leetcode_grind75#5** (C++)\n\n主なコメント(非常に充実):\n- C++では文字列はmutable、Pythonの`+=`での文字列コピーとは異なる\n- 関数の引数は`const string&`か`string_view`で渡すべき(コピー回避)\n- 戻り値のコピーを避けるには引数に参照を渡す方法もある(ただしNRVOで最適化される可能性あり)\n- `reserve`で事前にcapacityを確保する選択肢\n- メモリコピーの実行時間見積もり(1 clock で4-8バイト程度)\n- Google Style Guideでは`public`を先に宣言\n- Rope、Piece Tableなどのデータ構造の紹介\n\n---\n\n**4. huyfififi/coding-challenges#5** (Python)\n\n主なコメント:\n- `str.isalnum()`のドキュメントを確認すべき\n- `isalnum()`は"四"や"²"などUnicodeの数字も`True`を返すため、問題の意図と異なる可能性がある → 面接では確認が必要\n\n---\n\n**5. kzhra/Grind41#5** (C++)\n\n主なコメント:\n- `else`を省いてインデントを浅くする\n- `==`のときはループを抜けて良い\n- Google Style Guide準拠の空白の入れ方(キーワード後、`{`前、`:`前後など)\n- `std::tolower()`と`isalnum()`を組み合わせてif文を簡潔に\n- `continue`の一貫性、不要な`continue`は削除\n- early returnで条件を反転した方が読みやすい\n- 変数名は`left`/`right`の方が直感的\n\n---\n\n**6. t-ooka/leetcode#6** (Python/C++)\n\n主なコメント:\n- `i`/`j`より`left`/`right`の方が分かりやすい\n- `!= True`より`not X`の方が自然\n- whileが連続すると読みにくい → `if not ... : continue`パターンの提案\n- PEP8ではwhileと同じ行に実行文を置くことは非推奨\n\n---\n\n**共通して多かった指摘:**\n1. 変数名は`left`/`right`が直感的\n2. early returnでインデントを浅く\n3. `isalnum()`の挙動(Unicode対応)に注意\n4. 不要な`continue`や`else`は省略\n\n何か気になるPRがあれば、詳しく見ていきましょうか?\n\n- そのほか、コードを見た感想\n - https://github.com/huyfififi/coding-challenges/pull/5\n - step1_bの方法はシンプルで良いと思った。-> [実装3](#実装3)\n\n### 実装3\n\n- リファクタ内容([実装2](#実装2)をベースに改良)\n - isalpha() or isdecimal()の代わりに isalnum() を使う。\n - 二重ループだったのを一重ループに変更。\n - 手順の都合上、初期化が[実装2](#実装2)よりシンプルになった。\n- 計算量\n - Time: O(n)\n - Extra Space: O(1)\n\n```python\nclass Solution:\n def isPalindrome(self, s: str) -> bool:\n left = 0\n right = len(s) - 1\n while left < right:\n if not s[left].isalnum():\n left += 1\n continue\n \n if not s[right].isalnum():\n right -= 1\n continue\n \n if s[left].lower() != s[right].lower():\n return False\n left += 1\n right -= 1\n \n return True\n```\n\n## Step 3\n\n### 実装4\n\n- 何も見ずに書き直し\n- 可読性の高さから[実装1](#実装1)ベースの実装を選択。\n- alphanumericの判定には正規表現が必要十分で最適と判断。\n\n```python\nimport re\n\n\nclass Solution:\n def isPalindrome(self, s: str) -> bool:\n normalized = re.findall(r"[a-zA-Z0-9]", s.lower())\n left = 0\n right = len(normalized) - 1\n while left < right:\n if normalized[left] != normalized[right]:\n return False\n left += 1\n right -= 1\n return True\n```\n\n## Step 4\n\n自分のPR:(自分のPRへのリンク)\n\n**Q:** それぞれのレビューコメントについて、もう少し詳しく教えてください。\n\n### 実装5\n\n- レビューを受けて書き直し\n - (修正点)\n\n```python\nclass Solution:\n pass\n```'