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
331 changes: 331 additions & 0 deletions 2_add-two-numbers.md
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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

curry はカレー料理だと思います。 carry だと思います。

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();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ListNode dummy_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.

https://en.cppreference.com/w/cpp/language/default_initialization
デフォルト初期化、あまり分かってなくて避けちゃうんですよね。

if T is a (possibly cv-qualified) non-POD(until C++11) class type, the constructors are considered and subjected to overload resolution against the empty argument list. The constructor selected (which is one of the default constructors) is called to provide the initial value for the new object;

ListNodeは non POD (is_pod<ListNode>::valuefalse でした) なのでデフォルトコンストラクタが呼ばれるってことなんですかね...ただC++11までと書いてあるしPODの条件も良くわからない。
今回はコピー初期化の書き方になってましたが、大体 ListNode foo = {}; のリスト初期化を使います。こちらならどうでしょうか?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

「(until C++11)」の部分が何を表しているのか、自分も分かりませんでした。

ListNode foo = {}; はあまり見ないように思います。

Google C++ Style Guide には、明示的な言及は見つけられませんでしたが、
ListNode dummy_head;
相当の書き方が例示されているようです。
https://google.github.io/styleguide/cppguide.html#Local_Variables

C++ 得意な方、補足をお願いできますでしょうか…。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

えー、私もこれは手を出したくないやつです。

C 言語の struct はメモリーの初期化がなされないので確保が速く、そこに memcpy したりします。C++ はこれを引き継いでいるわけですが、vector などでこれをやられたら大事件です。こういったものを区別して扱わないといけません。
C++11で、POD (Plain Old Data、C 言語由来) が trivial, trivially_copyable, standard_layout などに概念が整理されたためにその注釈がついていますね。実用上はあまり変わっていなさそうです。

あとは、未定義動作ですかね。プログラムは未定義動作が含まれていたらなんでもありなので、含まれないことを仮定して最適化していいという利点があり、その境界はきちんと決めないといけません。char はおそらく文字列として使われるので、未定義動作になりにくくなっているみたいですね。

リスト初期化はあまり見慣れていないのですが、これは C++11 からの機能で、私は03くらいまでの機能で書いていたというだけのことかと思います。

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.

うーんリファレンスやGoogle C++ Style Guide見てみましたが判断が難しい...

リスト初期化はあまり見慣れていないのですが、これは C++11 からの機能で、私は03くらいまでの機能で書いていたというだけのことかと思います。

なるほどです。じゃあいったんリスト初期化でいこうかな、と思います。未初期化の変数関係の未定義動作も嫌ですし...何かご意見ありましたら後でも良いので是非。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

記事ありがとうございます。
ListNodeはUser-Provided Constructorsを定義しているので、default-initializationが実行されたときはそちらが呼ばれる。で、そのdefault constructorの定義は ListNode() : val(0), next(nullptr) {} なので、data memberが不定になることはない。
...という感じですかね。この挙動に毎回思いを馳せるのは辛いので、記事にも

As these rules are not intuitive, the easiest rule to remember to ensure an object is initialized is to provide an initializer. This is called value-initialization and is distinct from default-initialization, which is what the compiler will perform otherwise for scalar and aggregate types.

と書いてるので明示的に初期化子をつけようと思います。あと

struct Foo {
  int v = 0;
};

のようなdefault member initializationも良さそうですね (シンプルな構造体だったらこっちかなぁ...)。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0 <= Node.val <= 9という条件なので、念の為dummy_headの初期値をこれ以外(-1とか)にしておいた方が良い気がします。

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) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Defaultだと何を返すのか少しわかりにくい気がしたのでgetValOfNodeなどで良い気がしましたが, どうでしょうか

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.

getValOfNodeだと引数がnullptrのときにどうなるのか迷うかもと感じました。
ただdefaultが何なのか関数名を見ただけでは分からず、かつその値がアルゴリズムにおいて重要なのはそうだなと思ったので getValOrZeroとかにした方がいいかもな、と思いました

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

getValOrZeroもいいですが、

static int getValOrDefault(const ListNode* const node, int default_val);

const int total = getValOrDefault(l1, /*default_val=*/0) + getValOrDefault(l2, /*default_val=*/0) + curry;

としてもいいかもしれません。

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.

たしかに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) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

51edcc9#diff-63ff0854b4840d6c1c97b191d3a4dc35391f9e6b3f3fd9b9bcc38c7e3d1068d2R284-R286
こんな感じですかね。しかしenum classを使うのはともかく、kのprefix付けるのはハンガリアン記法だと思うのですが、このルールに従うメリットってどういうものなのでしょうか?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

スタイルガイドには、マクロとの区別と書いてありますね。enum classならそもそもcollisionが発生しないかもしれないですが…
https://stackoverflow.com/questions/5016622/where-does-the-k-prefix-for-constants-come-from

};

struct DigitInfo {
ListNode* prev;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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;
}
};
```