-
Notifications
You must be signed in to change notification settings - Fork 0
2. Add Two Numbers #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,331 @@ | ||
| # 2_add-two-numbers.md | ||
|
|
||
| ## 1st | ||
|
|
||
| 所要時間: 17:12 | ||
|
|
||
| n: l1, l2それぞれのノード数の大きい方 | ||
| - 時間計算量: O(n) | ||
| - 空間計算量: O(n) | ||
|
|
||
| 一度解いたことがあるにしては時間がかかりすぎている。 `l1 || l2 || curry` のorをandと間違えて1ミス、解消に手間取る。 | ||
|
|
||
| - 変数名addedはadded_value_nodeか。単にnodeでも十分かも。先頭がdummyで最初はdummyを指すので名前がつけづらい | ||
| - while内のvalという変数名はtotalくらいか。個人的にはvalを算出する計算が分かりやすいので許容範囲かという気はする | ||
|
|
||
| ```cpp | ||
| /** | ||
| * Definition for singly-linked list. | ||
| * struct ListNode { | ||
| * int val; | ||
| * ListNode *next; | ||
| * ListNode() : val(0), next(nullptr) {} | ||
| * ListNode(int x) : val(x), next(nullptr) {} | ||
| * ListNode(int x, ListNode *next) : val(x), next(next) {} | ||
| * }; | ||
| */ | ||
| class Solution { | ||
| public: | ||
| ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { | ||
| ListNode* dummy_head = new ListNode(0); | ||
| ListNode* added = dummy_head; | ||
| int curry = 0; | ||
| while (l1 || l2 || curry) { | ||
| const int val = getValueOrDefault(l1) + getValueOrDefault(l2) + curry; | ||
| const int digit = val % 10; | ||
| curry = val / 10; | ||
| added->next = new ListNode(digit); | ||
| added = added->next; | ||
| l1 = getNextIfExists(l1); | ||
| l2 = getNextIfExists(l2); | ||
| } | ||
| return dummy_head->next; | ||
| } | ||
|
|
||
| private: | ||
| static int getValueOrDefault(const ListNode* const node) { | ||
| if (!node) return 0; | ||
| return node->val; | ||
| } | ||
|
|
||
| static ListNode* getNextIfExists(const ListNode* const node) { | ||
| if (!node) return nullptr; | ||
| return node->next; | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ## 2nd | ||
|
|
||
| ### 参考 | ||
|
|
||
| - https://discord.com/channels/1084280443945353267/1231966485610758196/1237404231225577592 | ||
| - https://discord.com/channels/1084280443945353267/1225849404037009609/1233844967777243318 | ||
| - https://discord.com/channels/1084280443945353267/1233603535862628432/1233768573718106253 | ||
| - https://discord.com/channels/1084280443945353267/1200089668901937312/1206949445359640576 | ||
|
|
||
| 再帰バージョンで書いた。 | ||
|
|
||
| 所要時間: 10:12 | ||
|
|
||
| n: l1, l2それぞれのノード数の大きい方 | ||
| - 時間計算量: O(n) | ||
| - 空間計算量: O(n) | ||
|
|
||
| リストのサイズが高々100なのでスタックオーバーフローすることはないだろう。 | ||
| スタックサイズ8MBとして、スタックフレーム辺りの引数、ローカル変数でポインタ3つ、intが2つ + ベースポインタとリターンアドレスで48byte+αくらいだろうか?とすると 8*2**20/48 ≒ 170K回くらいは深くできそう。 | ||
| (ポインタやレジスタのサイズを64bitとして計算している) | ||
|
|
||
| ```cpp | ||
| class Solution { | ||
| public: | ||
| ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { | ||
| return addTwoNumbersHelper(l1, l2, 0); | ||
| } | ||
|
|
||
| private: | ||
| static ListNode* addTwoNumbersHelper( | ||
| const ListNode* const digit_node1, | ||
| const ListNode* const digit_node2, | ||
| const int curry | ||
| ) { | ||
| if (!digit_node1 && !digit_node2 && !curry) return nullptr; | ||
| ListNode* const digit_node = new ListNode(0); | ||
| const int total = getValOrDefault(digit_node1) + getValOrDefault(digit_node2) + curry; | ||
| digit_node->val = total % 10; // digit | ||
| digit_node->next = addTwoNumbersHelper(getNextIfExists(digit_node1), getNextIfExists(digit_node2), total / 10); | ||
| return digit_node; | ||
| } | ||
|
|
||
| static int getValOrDefault(const ListNode* const node) { | ||
| if (!node) return 0; | ||
| return node->val; | ||
| } | ||
|
|
||
| static ListNode* getNextIfExists(const ListNode* const node) { | ||
| if (!node) return nullptr; | ||
| return node->next; | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
|
|
||
| ## 3rd | ||
|
|
||
|
|
||
| ```cpp | ||
| class Solution { | ||
| public: | ||
| ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { | ||
| ListNode dummy_head = ListNode(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://en.cppreference.com/w/cpp/language/default_initialization
ListNodeは non POD ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 「(until C++11)」の部分が何を表しているのか、自分も分かりませんでした。
Google C++ Style Guide には、明示的な言及は見つけられませんでしたが、 C++ 得意な方、補足をお願いできますでしょうか…。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. えー、私もこれは手を出したくないやつです。 C 言語の struct はメモリーの初期化がなされないので確保が速く、そこに memcpy したりします。C++ はこれを引き継いでいるわけですが、vector などでこれをやられたら大事件です。こういったものを区別して扱わないといけません。 あとは、未定義動作ですかね。プログラムは未定義動作が含まれていたらなんでもありなので、含まれないことを仮定して最適化していいという利点があり、その境界はきちんと決めないといけません。char はおそらく文字列として使われるので、未定義動作になりにくくなっているみたいですね。 リスト初期化はあまり見慣れていないのですが、これは C++11 からの機能で、私は03くらいまでの機能で書いていたというだけのことかと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. うーんリファレンスやGoogle C++ Style Guide見てみましたが判断が難しい...
なるほどです。じゃあいったんリスト初期化でいこうかな、と思います。未初期化の変数関係の未定義動作も嫌ですし...何かご意見ありましたら後でも良いので是非。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 記事ありがとうございます。
と書いてるので明示的に初期化子をつけようと思います。あと struct Foo {
int v = 0;
};のようなdefault member initializationも良さそうですね (シンプルな構造体だったらこっちかなぁ...)。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| ListNode* node = &dummy_head; | ||
| int curry = 0; | ||
| while (l1 || l2 || curry) { | ||
| const int total = getValOrDefault(l1) + getValOrDefault(l2) + curry; | ||
| curry = total / 10; | ||
| node->next = new ListNode(total % 10); // digit | ||
| node = node->next; | ||
| l1 = getNextIfExists(l1); | ||
| l2 = getNextIfExists(l2); | ||
| } | ||
| return dummy_head.next; | ||
| } | ||
|
|
||
| private: | ||
| static int getValOrDefault(const ListNode* const node) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Defaultだと何を返すのか少しわかりにくい気がしたのでgetValOfNodeなどで良い気がしましたが, どうでしょうか
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getValOfNodeだと引数がnullptrのときにどうなるのか迷うかもと感じました。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getValOrZeroもいいですが、 としてもいいかもしれません。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. たしかにgetOrDefaultで期待するシグネチャはそれかもですね |
||
| if (!node) return 0; | ||
| return node->val; | ||
| } | ||
|
|
||
| static ListNode* getNextIfExists(const ListNode* const node) { | ||
| if (!node) return nullptr; | ||
| return node->next; | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ## 4th | ||
|
|
||
| ```cpp | ||
| class Solution { | ||
| public: | ||
| ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { | ||
| ListNode dummy_head = ListNode(-1); | ||
| ListNode* node = &dummy_head; | ||
| int carry = 0; | ||
| while (l1 || l2 || carry) { | ||
| const int total = getValOrDefault(l1, 0) + getValOrDefault(l2, 0) + carry; | ||
| carry = total / 10; | ||
| node->next = new ListNode(total % 10); // digit | ||
| node = node->next; | ||
| l1 = getNextIfExists(l1); | ||
| l2 = getNextIfExists(l2); | ||
| } | ||
| return dummy_head.next; | ||
| } | ||
|
|
||
| private: | ||
| static int getValOrDefault(const ListNode* const node, const int default_value) { | ||
| if (!node) return default_value; | ||
| return node->val; | ||
| } | ||
|
|
||
| static ListNode* getNextIfExists(const ListNode* const node) { | ||
| if (!node) return nullptr; | ||
| return node->next; | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ## 5th | ||
|
|
||
| スタックで行きがけの再帰 | ||
|
|
||
| ```cpp | ||
| struct DigitInfo { | ||
| ListNode* prev_added; | ||
| ListNode* digit_node1; | ||
| ListNode* digit_node2; | ||
| int carry; | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 帰りがけの処理がないなら、簡単にiterativeに書き直せますよね。 |
||
| ListNode dummy_head = {}; | ||
| stack<DigitInfo> digit_info_stack = {}; | ||
| digit_info_stack.push({&dummy_head, l1, l2, 0}); | ||
| while (!digit_info_stack.empty()) { | ||
| auto [prev, digit_node1, digit_node2, carry] = digit_info_stack.top(); | ||
| digit_info_stack.pop(); | ||
| if (!digit_node1 && !digit_node2 && !carry) continue; | ||
| const int total = getValOrDefault(digit_node1, 0) + getValOrDefault(digit_node2, 0) + carry; | ||
| prev->next = new ListNode(total % 10); | ||
| digit_info_stack.push({prev->next, getNextIfExists(digit_node1), getNextIfExists(digit_node2), total / 10}); | ||
| } | ||
| return dummy_head.next; | ||
| } | ||
|
|
||
| private: | ||
| static int getValOrDefault(const ListNode* const node, const int default_value) { | ||
| if (!node) return default_value; | ||
| return node->val; | ||
| } | ||
|
|
||
| static ListNode* getNextIfExists(const ListNode* const node) { | ||
| if (!node) return nullptr; | ||
| return node->next; | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| スタックで帰りがけの再帰 | ||
|
|
||
| ```cpp | ||
| enum RecursiveDirection { | ||
| go, back | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 51edcc9#diff-63ff0854b4840d6c1c97b191d3a4dc35391f9e6b3f3fd9b9bcc38c7e3d1068d2R284-R286 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. スタイルガイドには、マクロとの区別と書いてありますね。enum classならそもそもcollisionが発生しないかもしれないですが… |
||
| }; | ||
|
|
||
| struct DigitInfo { | ||
| ListNode* prev; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. もともとの帰りがけの再帰だとprevがなかったですよね。そもそもprevがあったら、簡単に行き掛けにできるはずです。 |
||
| ListNode* current; | ||
| ListNode* digit_node1; | ||
| ListNode* digit_node2; | ||
| int carry; | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { | ||
| ListNode* head = new ListNode(0); | ||
| stack<pair<RecursiveDirection, DigitInfo>> digit_info_stack = {}; | ||
| digit_info_stack.push({RecursiveDirection::go, {nullptr, head, l1, l2, 0}}); | ||
| while (!digit_info_stack.empty()) { | ||
| auto [direction, info] = digit_info_stack.top(); | ||
| auto [prev, current, digit_node1, digit_node2, carry] = info; | ||
| digit_info_stack.pop(); | ||
| if (direction == RecursiveDirection::go) { | ||
| if (!digit_node1 && !digit_node2 && !carry) continue; | ||
| int total = getValOrDefault(digit_node1, 0) + getValOrDefault(digit_node2, 0) + carry; | ||
| current->val = total % 10; | ||
| digit_info_stack.push({RecursiveDirection::back, {prev, current, nullptr, nullptr, 0}}); | ||
| digit_info_stack.push({ | ||
| RecursiveDirection::go, | ||
| {current, new ListNode(), getNextIfExists(digit_node1), getNextIfExists(digit_node2), total / 10} | ||
| }); | ||
| } else { | ||
| // back | ||
| if (prev) { | ||
| prev->next = current; | ||
| } | ||
| } | ||
| } | ||
| return head; | ||
| } | ||
|
|
||
| private: | ||
| static int getValOrDefault(const ListNode* const node, const int default_value) { | ||
| if (!node) return default_value; | ||
| return node->val; | ||
| } | ||
|
|
||
| static ListNode* getNextIfExists(const ListNode* const node) { | ||
| if (!node) return nullptr; | ||
| return node->next; | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ## 6th | ||
|
|
||
| 帰りがけstack版。enumはboolで十分かもしれないが、一応。 | ||
|
|
||
| ```cpp | ||
| enum class RecursiveDirection { | ||
| kGo, kBack | ||
| }; | ||
|
|
||
| struct DigitInfo { | ||
| RecursiveDirection direction; | ||
| ListNode*& prev; | ||
| ListNode* node; | ||
| int digit; | ||
| ListNode* l1; | ||
| ListNode* l2; | ||
| int carry; | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { | ||
| ListNode* head = nullptr; | ||
| stack<DigitInfo> digit_info_stack = {}; | ||
| digit_info_stack.push({RecursiveDirection::kGo, head, nullptr, 0, l1, l2, 0}); | ||
| while (!digit_info_stack.empty()) { | ||
| auto& [direction, prev, node, digit, l1, l2, carry] = digit_info_stack.top(); | ||
| if (direction == RecursiveDirection::kBack) { | ||
| prev = new ListNode(digit, node); | ||
| digit_info_stack.pop(); | ||
| continue; | ||
| } | ||
| if (!l1 && !l2 && !carry) { | ||
| digit_info_stack.pop(); | ||
| continue; | ||
| } | ||
| int total = carry; | ||
| if (l1) { | ||
| total += l1->val; | ||
| l1 = l1->next; | ||
| } | ||
| if (l2) { | ||
| total += l2->val; | ||
| l2 = l2->next; | ||
| } | ||
| digit = total % 10; | ||
| direction = RecursiveDirection::kBack; | ||
| digit_info_stack.push({RecursiveDirection::kGo, node, nullptr, 0, l1, l2, total / 10}); | ||
| } | ||
| return head; | ||
| } | ||
| }; | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curryはカレー料理だと思います。carryだと思います。