Skip to content
Open
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
177 changes: 177 additions & 0 deletions 206.-Reverse-Linked-List/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# step1
- スタックのセクションにあったのでとりあえずスタックで解いた。
- 素直に書いてガチャガチャ変えて動いてしまった。よくない。
```cpp:step1.cpp
#include <stack>

class Solution {
public:
ListNode* reverseList(ListNode* head) {
std::stack<ListNode*> st;
ListNode* tail = head;
// nullをはじくのを統一的に書けないか?
if (head == nullptr) {
return nullptr;
}
while(tail != nullptr) {
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 のあとにスペースを空けることをお勧めします。

参考までにスタイルガイドへのリンクを貼ります。

https://google.github.io/styleguide/cppguide.html#Horizontal_Whitespace

if (b) { // Space after the keyword in conditions and loops.

ただし、上記のスタイルガイドは唯一絶対のルールではなく、複数あるスタイルガイドの一つに過ぎないということを念頭に置くことをお勧めします。また、所属するチームにより何が良いとされているかは変わります。自分の中で良い書き方の基準を持ちつつ、チームの平均的な書き方で書くことをお勧めいたします。

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.

たびたびご指摘頂きすみません。自分の中でルールを決めて気づけるように気をつけます。

st.push(tail);
tail = tail->next;
}
head = st.top();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ざっと読んだ時に、この行を読み飛ばしていて、headは入力リストの先頭ではないのか?と一瞬混乱しました。
この関数内では、headは入力リストの先頭として意味を崩さず、20行目は、後の方の解法のようにreversed_headとした方がわかりやすく感じます。

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.

ありがとうございます。おっしゃる通り、最初書いたときは引数のheadを戻り値に流用していたのですが、一つのプログラム内で変数の意味合いが変わるのは紛らわしいと思いやめました。

st.pop();
tail = head;
while(!st.empty()) {
tail->next = st.top();
st.pop();
tail = tail->next;
}
// これってなんで必要?
tail->next = nullptr;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これがないとreverseした最後のNodeと最後から二番目のNodeがお互いを指してcycleになる気がしますね

return head;
}
};

```

# step2
- 再帰で書けることに気づく。
```cpp:step2.cpp
class Solution {
public:
ListNode* reverseList(ListNode* head) {
return reverseRecursively(head);
}
private:
// node以降のリストを逆順に繋ぎなおし、元々の最後尾を新たなheadとして返す
ListNode* reverseRecursively(ListNode* node) {
if (node == nullptr || node->next == nullptr) {
return node;
}
ListNode* head = reverseRecursively(node->next);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

スペースは 1 つで十分だと思います。

node->next->next = 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.

これは個人の感想で他の方々の意見を伺いたいですが、nextのnextにアクセスするのって頭の中で画を想像しづらくて結構トリッキーだと感じるんですよね。返り値で(reversed_head, reversed_tail)の二つを返すか、引数で(reversing, appending)を受け取る方法 (参考)の方がなんとなく想像しやすい解法のような気がします。

Copy link
Copy Markdown
Owner Author

@maeken4 maeken4 Jun 1, 2025

Choose a reason for hiding this comment

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

たしかにうまく再帰する部分問題に分けれていない感じがありますね…ご参考で頂いた書き方がスタイリッシュでいいですね!

    // [1,2,3], [4,5,6] -> [3,2,1,4,5,6]
    // [], [4,5,6] -> [4,5,6]
    ListNode* reverse_and_append(ListNode* head1, ListNode* head2){
        if (head1 == nullptr) {
            return head2;
        }
        auto new_head1 = head1->next;
        head1->next = head2;
        return reverse_and_append(new_head1, head1);

    }

node->next = nullptr;
return head;
}
};
```
人のコードを見る。
- step1ですっきりしなかった部分も番兵を使うときれいに書ける([linoahhhさん](https://github.com/lilnoahhh/leetcode/pull/10/files/31a6eddb4e971614db2b6d2fb67da1a44fb764c4#r1948006046))。
- スタックに入っているノードを取り出してつなげる、というのに最後の場合分けが生じなくてよくなる。
```cpp
#include <stack>

class Solution {
public:
ListNode* reverseList(ListNode* head) {
std::stack<ListNode*> st;
ListNode* sentinel = nullptr;
st.push(sentinel);
ListNode* tail = head;
while (tail) {
st.push(tail);
tail = tail->next;
}

ListNode* new_head = st.top();st.pop();
tail = new_head;
while (!st.empty()) {
tail->next = st.top();
st.pop();
tail = tail->next;
}
return head;
}
};
```


- もっと素直に前後を入れ替えていく([tarinaihitoriさん](https://discord.com/channels/1084280443945353267/1295357747545505833/1298524551592018003))
```cpp:step2_another.cpp
class Solution {
public:
ListNode* reverseList(ListNode* head){
// 反転済みリストの先頭
ListNode* reversed_head = nullptr;
ListNode* cur_node = head;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

cur_node という変数名は読む助けにほとんどならないのでもう少し良いものが欲しいですね。

「変数名をつける」という行為も、比較的小さいものであるにしても技術的選択であり、その技術的選択はなんらかの意味でエンジニアリングに繋げて判断する、ということです。

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.

ありがとうございます。いまいちcur_nodeがよくないという感覚が持てなかったのですが、現にこのコードで直後に変数名を間違えており意味のある命名の必要性を感じました。

while (cur_node) {
ListNode* tmp = cur_node->next;
tail->next = reversed_head;
reversed_head = cur_node;
cur_node = tmp;
}
return reversed_head;
}
};
```

シンプルな問だけどけっこう方針が違う解法があって面白い。


# step3
スタック
```cpp:step3.cpp
#include <stack>
class Solution {
public:
ListNode* reverseList(ListNode* head) {
std::stack<ListNode*> st;
auto sentinel = nullptr;
st.push(sentinel);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

単にst.push(nullptr)でもよい気がします

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.

たしかに変数を置かずともコメントで十分ですね。

auto tail = head;
while (tail) {
st.push(tail);
tail = tail->next;
}

auto new_head = st.top();
st.pop();
tail = new_head;
while (tail) {
tail->next = st.top();
st.pop();
tail = tail->next;
}
return new_head;
}
};
```

シンプルな交換
```cpp
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* reversed_head = nullptr;
ListNode* cur_node = head;
while (cur_node) {
auto tmp = cur_node->next;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

tmpという名前だけだと、cur_nodeにtmpを代入するときにあれtmpってなんだったっけ、とこの行に戻って確認する必要が出てきてしまう気がするので、next_nodeとかsuccessorなどより説明的な変数名を割り当てても良いと思いました。

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.

ありがとうございます。nextだとnextがそのループを抜けるとcurrentになり…とか考えると下手に意味を持たせないほうがいいかなと思いましたが、もっと距離が離れたりするとtmpではわからなくなりそうですね。successorは空間的なニュアンスがありわかりやすいですね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

個人的には original_next_node などを使おうか考えたりしました。もっとよい命名はありそうだと思います。

cur_node->next = reversed_head;
reversed_head = cur_node;
cur_node = tmp;
}
return reversed_head;
}
};
```

再帰別解(https://github.com/maeken4/Arai60/pull/7/files/e30de22cd54e0c4dab8713af400534b11d6adb69#r2118170691)
```cpp
class Solution {
public:
ListNode* reverseList(ListNode* head) {
return reverse_and_append(head, nullptr);
}
private:
// [1,2,3], [4,5,6] -> [3,2,1,4,5,6]
// [], [4,5,6] -> [4,5,6s]
ListNode* reverse_and_append(ListNode* head1, ListNode* head2){
if (head1 == nullptr) {
return head2;
}
auto new_head1 = head1->next;
head1->next = head2;
return reverse_and_append(new_head1, head1);

}
};
```