-
Notifications
You must be signed in to change notification settings - Fork 0
Create 78. Subsets.md #50
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,212 @@ | ||
| # **78. Subsets** | ||
|
|
||
| # 進め方 | ||
|
|
||
| Step1 : 問題を解く。 | ||
|
|
||
| Step2 : 他の人のPRを参照し、コメントする。 | ||
|
|
||
| Step3 : 3回続けてエラーが出ないように書く。ドキュメントを参照する。 | ||
|
|
||
| # 実践 | ||
|
|
||
| ## Step1 | ||
|
|
||
| ### 思考ログ | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: List[int]) -> List[List[int]]: | ||
| def subsets_helper(subset: List[int], rest_nums) -> List[List[int]]: | ||
| if not rest_nums: | ||
| return [subset] | ||
| result = [] | ||
| result.append(subset) | ||
| for i, num in enumerate(rest_nums): | ||
| target = subset[:] | ||
| target.append(num) | ||
| result.append(target) | ||
| next_rest = rest_nums[:i] + rest_nums[i+1:] | ||
| next_subsets = subsets_helper(target, next_rest) | ||
| result.extend(next_subsets) | ||
| return result | ||
|
|
||
| return subsets_helper([], nums) | ||
| ``` | ||
|
|
||
| 樹形図を想像して、書いたがWA。 | ||
|
|
||
| 並び替えで区別してしまっていた。 | ||
|
|
||
| 2**n個なので何か使えないかな。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: List[int]) -> List[List[int]]: | ||
| def subsets_helper(subset: List[int], index: int) -> List[List[int]]: | ||
| if index == len(nums): | ||
| return [subset] | ||
| result = [] | ||
| # not contain | ||
| result.extend(subsets_helper(subset, index + 1)) # copy_subsetと書いてしまうとWA | ||
|
|
||
| # contain | ||
| copy_subset = subset[:] | ||
| copy_subset.append(nums[index]) | ||
| result.extend(subsets_helper(copy_subset, index + 1)) | ||
| return result | ||
|
|
||
| return subsets_helper([], 0) | ||
| ``` | ||
|
|
||
| 配列を共有してしまって沼にはまった。 | ||
|
|
||
| 計算量は時間空間ともにO(2^n) 1024回のループ | ||
|
|
||
| 10msくらい | ||
|
|
||
| 1. **ベースケースでの参照渡し (`return [subset]`)** | ||
| - `if index == len(nums): return [subset]` | ||
| - ここで返される `subset` は、呼び出し元から渡されたリストオブジェクトそのものです(またはその途中でコピーされたリストオブジェクト)。 | ||
| - もしこの `subset`(`subsets_helper` の中の `copy_subset` が深い階層でこれに相当する)が、この後呼び出し元のスタックフレームで変更されると、`result` に追加されたリストも変更されてしまいます。 | ||
| - **修正案**: `return [subset[:]]` または `return [list(subset)]` のように、リストの**コピー**を返す必要があります。これにより、`result` に追加される各サブセットが独立したものになります。 | ||
| 2. **`copy_subset` の変更と再利用** | ||
| - `copy_subset = subset[:]` で `subset` のコピーが作られます。 | ||
| - この `copy_subset` は、まず「`nums[index]` を含まない」場合の再帰呼び出し `subsets_helper(copy_subset, index + 1)` に渡されます。 | ||
| - この呼び出しが結果を返し(ベースケースでコピーが返されなかった場合、`copy_subset` への参照を含むリストが返る可能性があります)、`result` に追加されます。 | ||
| - **その後**、`copy_subset.append(nums[index])` によって、**先ほど「含まない」場合で使われたのと同じ `copy_subset` オブジェクトが変更されます。** | ||
| - もし、1つ目の再帰呼び出し `subsets_helper(copy_subset, ...)` が `copy_subset` への参照を結果として返していた場合、この `append` 操作によって、既に `result` に追加されたはずのサブセットが意図せず変更されてしまいます。これが「WA (Wrong Answer)」の主な原因です。 | ||
|
|
||
|
|
||
| これ難しい。 | ||
|
|
||
| 理由は分かるんですが、直観的ではないですね。 | ||
|
|
||
| 名前空間とオブジェクト空間の理屈は分かるのですが、再帰という形になるとデバッグできない。 | ||
|
|
||
| 慣れの問題でしょうか。 | ||
|
|
||
| ## Step2 | ||
|
|
||
| ### 同じ問題を解いた人のプルリクを見る | ||
|
|
||
| https://github.com/olsen-blue/Arai60/pull/52/files#diff-ddd8c09ee41837c8d5bde978403f850a0b08217fb8ec8eac6d0f2ae10e369d04 | ||
|
|
||
| - bitでの解き方とスタックでの解き方をしている | ||
| - 再起のやり方は副作用で値を書き換えているのが気になる。 | ||
| - なるべく純粋関数っぽくやりたいと感じます。 | ||
|
|
||
| https://github.com/hroc135/leetcode/pull/48/files | ||
|
|
||
| - 色々なやり方で試していて参考になる。 | ||
| - 末尾最適化、Pythonはないらしい。 | ||
| - https://qiita.com/pebblip/items/cf8d3230969b2f6b3132 | ||
| - https://discord.com/channels/1084280443945353267/1235971495696662578/1253008119458562119 | ||
|
|
||
| ちょっと書く練習が必要そうです。 | ||
|
|
||
| 解法2:bit | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: List[int]) -> List[List[int]]: | ||
| result = [] | ||
| for bit in range(1 << len(nums)): | ||
| subset = [] | ||
| for i in range(bit): | ||
| if bit & (1 << i): | ||
| subset.append(nums[i]) | ||
| result.append(subset) | ||
| return result | ||
| ``` | ||
|
|
||
| nums = [1,2,3] のとき | ||
|
|
||
| bitが001のときは subset = [1] になります。 | ||
|
|
||
| 一旦理解すると再現は簡単。 | ||
|
|
||
| 解法3:スタック | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: List[int]) -> List[List[int]]: | ||
| stack = [(0, [])] | ||
| result = [] | ||
| while stack: | ||
| index, subset = stack.pop() | ||
| if index >= len(nums): | ||
| result.append(subset) | ||
| continue | ||
| stack.append((index+1, subset)) | ||
| added_subset = subset + [nums[index]] | ||
| stack.append((index+1, added_subset)) | ||
| return result | ||
| ``` | ||
|
|
||
| ## Step3 | ||
|
|
||
| ### 3回連続で再現 | ||
|
|
||
| 解法1:再帰 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: List[int]) -> List[List[int]]: | ||
| def subsets_helper(subset, index): | ||
| if index == len(nums): | ||
| return [subset] | ||
| added_subset = subset + [nums[index]] | ||
| return subsets_helper(subset, index+1) + subsets_helper(added_subset, index+1) | ||
|
|
||
| return subsets_helper([], 0) | ||
| ``` | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: List[int]) -> List[List[int]]: | ||
| def helper(subset, index): | ||
| if index == len(nums): | ||
| return [subset] | ||
| added = subset + [nums[index]] | ||
| return helper(subset, index + 1) + helper(added, index + 1) | ||
|
|
||
| return helper([], 0) | ||
|
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. 個人的には認知負荷が高いような気がして、原因は 自分なら subset を随時入れていく箱をヘルパー関数の外に用意したくなりました。 class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
result = []
def subsets_helper(subset, index):
if index == len(nums):
result.append(subset)
return
added_subset = subset + [nums[index]]
subsets_helper(subset, index+1)
subsets_helper(added_subset, index+1)
subsets_helper([], 0)
return result
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. 副作用を嫌ってこう書きましたが、今見るとわかりにくいですね。
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. 私は副作用のせいで時間を溶かしたことがなんどかあるので、ほかの人より避けたい気持ちが強いかもしれません。 |
||
| ``` | ||
|
|
||
| このくらいシンプルでもよいかもしれない。 | ||
|
|
||
| 解法2:bit | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: List[int]) -> List[List[int]]: | ||
| result = [] | ||
| for bit in range(1 << len(nums)): | ||
| subset = [] | ||
| for i in range(bit): | ||
|
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. ここは |
||
| if bit & (1 << i): | ||
| subset.append(nums[i]) | ||
| result.append(subset) | ||
| return result | ||
| ``` | ||
|
|
||
| 解法3:スタック | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: List[int]) -> List[List[int]]: | ||
| result = [] | ||
| stack = [(0, [])] | ||
| while stack: | ||
| i, subset = stack.pop() | ||
| if i >= len(nums): | ||
| result.append(subset) | ||
| continue | ||
| added_subset = subset + [nums[i]] | ||
| stack.append((i+1, subset)) | ||
| stack.append((i+1, added_subset)) | ||
| return result | ||
| ``` | ||
|
|
||
| だいぶ分かってきた感覚になりました。 | ||
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.
この2行を
にしたら動きますかね。