Skip to content
Open
Show file tree
Hide file tree
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
28 changes: 28 additions & 0 deletions arai60/add-two-numbers/README-en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Considerations
- Solved this problem in the past
- Approach
- Keep track of carry while adding corresponding nodes one by one (similar to manual addition in your head)
- time: O(n), space: O(n)
- Pay special attention if a carry remains at the end
- Traversing the list can be done iteratively or recursively
- Can't think of any benefits for recursion, so will go with iteration
- Now to implement

## Step 1
- Implemented the algorithm considered above
- One concern is memory management
- Allocating new nodes on the stack won't work as memory is released when out of scope
- Created objects in the heap, but unsure who is responsible for managing these objects
- Haven't used C++ in a professional setting, but how is this handled in practice?
- Would using smart pointers for Linked List pointers help? -> Memory should be automatically released when references are gone

## Step 2
- Searched other people's PRs regarding memory management
- There might be limitations specific to LeetCode, but it's important to be aware of this issue
- Ref. https://github.com/Ryotaro25/leetcode_first60/pull/5#discussion_r1611301775
- Fixed by incorporating the final carry check into the main logic

## Step 3
- First time: 2m00s
- Second time: 1m58s
- Third time: 1m33s
29 changes: 29 additions & 0 deletions arai60/add-two-numbers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## 考察
- 過去に解いたことあり
- 方針
- 繰り上がりを保持しながら、対応するノードを一つずつ足していけばよさそう(頭の中では筆算を連想)
- time: O(n), space: O(n)
- 繰り上がりが最後に残った場合のみ注意
- リストの走査は繰り返しでも再帰でもOK
- 再帰にするメリットは思いつかないので繰り返しでいく
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

再帰に関しても時間/空間計算量やコードに落とす際の観点も踏まえた方が
なぜ @kazukiii さんが今回の実装を選ばれたのかがより伝わると思いました。
よく自分が受けている指摘ですが🙇

Copy link
Copy Markdown
Owner Author

@kazukiii kazukiii Jun 10, 2024

Choose a reason for hiding this comment

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

レビューありがとうございます。なぜ選択したのかも言語化して残すようにしてみます。

以下、今回の問題に関して考えることです。
再帰に関しては実装方法の違いだと考えており、時間/空間計算量は同じになります。
再帰処理に関しては、再帰的な関数呼び出しや呼び出しごとに生成されるスタックフレームを考えると、時間的にも空間的にもオーバーヘッドが多少あるため、今回繰り返しを選択しました。

一応再帰でも書いてみました->c9ba9b4

- あとは実装

## Step1
- 上で考えたアルゴリズムを実装
- 1つ気になるのがメモリ管理のところ
- 新しいノードをスタック領域に確保するとスコープを抜けた時点でメモリが解放されるのでうまくいかない
- ヒープ領域にオブジェクトを作成したけど、このオブジェクトの管理は誰の責務になるのかがわかっていない
- C++実務で使ったことないが、実務だとどうやるんだろう
- Linked Listのポインタをスマートポインタにすればよさそう? -> 参照が切れた時点で自動的にメモリが解放されそう
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.

ありがとうございます。理解が深まりました。

  • 動的に確保されたオブジェクトには所有者を設定する
  • その所有者がオブジェクトのメモリ管理に関する責任を持つ
  • 基本的には、所有権はuniqueにする(std::unique_ptrを使う)
  • 以上を元に実務では以下のようにすればいいと考えました
    • Linked Listのポインタをstd::unique_ptrで定義する
    • std::make_uniqueでスマートポインタを生成
    • Linked Listに追加する際に、所有権をmoveする


## Step2
- メモリ管理について他の人のPRをいくつか検索してみた
- LeetCode上の制限という問題もありそう。ただ問題意識は常に持っていたい。
- Ref. https://github.com/Ryotaro25/leetcode_first60/pull/5#discussion_r1611301775
- 最後にcarryをチェックして、1を追加していたところをメインロジックに吸収できたので修正した

## Step3
- 1回目: 2m00s
- 2回目: 1m58s
- 3回目: 1m33s

39 changes: 39 additions & 0 deletions arai60/add-two-numbers/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* 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* list1, ListNode* list2) {
int carry = 0;
ListNode dummy_head;
ListNode* node = &dummy_head;
while (list2 || list1) {
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 (list1 || list2)の方が違和感ないと思いました🙇

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.

気付きませんでした。ご指摘ありがとうございます。

int sum = carry;
if (list1) {
sum += list1->val;
list1 = list1->next;
}
if (list2) {
sum += list2->val;
list2 = list2->next;
}

carry = sum / 10;
node->next = new ListNode(sum % 10);
node = node->next;
}

if (carry == 1) {
node->next = new ListNode(1);
}

return dummy_head.next;
}
};
43 changes: 43 additions & 0 deletions arai60/add-two-numbers/step1_recursion.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* 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* list1, ListNode* list2) {
ListNode dummy_head;
ListNode* node = &dummy_head;
int carry = 0;

add(list1, list2, carry, node);

return dummy_head.next;
}

private:
void add(ListNode* list1, ListNode* list2, int carry, ListNode* node) {
if (!list1 && !list2 && !carry) return;

int sum = carry;
if (list1) {
sum += list1->val;
list1 = list1->next;
}
if (list2) {
sum += list2->val;
list2 = list2->next;
}

carry = sum / 10;
node->next = new ListNode(sum % 10);
node = node->next;

add(list1, list2, carry, node);
}
};
35 changes: 35 additions & 0 deletions arai60/add-two-numbers/step2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 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* list1, ListNode* list2) {
int carry = 0;
ListNode dummy_head;
ListNode* node = &dummy_head;
while (list1 || list2 || carry) {
int sum = carry;
if (list1) {
sum += list1->val;
list1 = list1->next;
}
if (list2) {
sum += list2->val;
list2 = list2->next;
}

carry = sum / 10;
node->next = new ListNode(sum % 10);
node = node->next;
}

return dummy_head.next;
}
};
35 changes: 35 additions & 0 deletions arai60/add-two-numbers/step3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 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* list1, ListNode* list2) {
ListNode dummy_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.

一応、dummy を使わない方法も書いてみますか?
(足すときに条件分岐します。)

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.

dummyを使わない方法も書いてみました。-> a9c7637

ListNode* node = &dummy_head;
int carry = 0;
while (list1 || list2 || carry) {
int sum = carry;
if (list1) {
sum += list1->val;
list1 = list1->next;
}
if (list2) {
sum += list2->val;
list2 = list2->next;
}

carry = sum / 10;
node->next = new ListNode(sum % 10);
node = node->next;
}

return dummy_head.next;
}
};
40 changes: 40 additions & 0 deletions arai60/add-two-numbers/step3_without_dummy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* 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* list1, ListNode* list2) {
ListNode* head = nullptr;
ListNode* node = nullptr;
int carry = 0;
while (list1 || list2 || carry) {
int sum = carry;
if (list1) {
sum += list1->val;
list1 = list1->next;
}
if (list2) {
sum += list2->val;
list2 = list2->next;
}

carry = sum / 10;
if (!head) {
head = new ListNode(sum % 10);
node = head;
continue;
}
node->next = new ListNode(sum % 10);
node = node->next;
}

return head;
}
};