-
Notifications
You must be signed in to change notification settings - Fork 0
Solve 78_subsets_medium #8
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
Open
Kaichi-Irie
wants to merge
6
commits into
main
Choose a base branch
from
78_subsets_medium
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
ca705ea
Solve 78_subsets_medium
Kaichi-Irie da534d9
Fix formatting in README and add link for Subsets II problem
Kaichi-Irie 9e317fd
Implement recursive solution for generating subsets in 78_subsets_medium
Kaichi-Irie 0af1c4d
Update README to clarify backtracking explanation and add details on …
Kaichi-Irie 646059b
Update README and add new solutions for generating subsets using stac…
Kaichi-Irie efc367e
Update README
Kaichi-Irie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # 問題へのリンク | ||
| [Subsets - LeetCode](https://leetcode.com/problems/subsets/description/) | ||
|
|
||
|
|
||
| # 言語 | ||
| Python | ||
|
|
||
| # 問題の概要 | ||
| 与えられた整数のリストから、すべての部分集合を生成する問題。 | ||
|
|
||
|
|
||
| # 自分の解法 | ||
| ## step1:ビット全探索 | ||
| 全ての部分集合は、`nums`の要素数を`n`としたとき、`2^n`通り存在する。各部分集合は、`0`から`2^n - 1`までの整数をビットマスクとして解釈することで生成できる。 | ||
|
|
||
| - 時間計算量:`O(n * 2^n)` | ||
| - 空間計算量:`O(n * 2^n)` | ||
|
|
||
| ## step2 | ||
| - `_ith_binary_digit`関数を定義して、整数をビットマスクとして解釈する際に、`i`番目のビットが立っているかどうかを判定する処理を分離した。 | ||
|
|
||
| # 別解1. backtracking | ||
| - backtrackingを用いて部分集合を生成する方法。再帰的に要素を選択するかどうかを決定し、部分集合を構築していく。`subset`リストはオブジェクトの操作で更新され、最後に`all_subsets`に追加するときにコピーされる。そのため、思わぬところに影響が出ないように更新の順序に注意が必要。 | ||
| - なお、Pythonの値渡し、参照渡し、参照の値渡しについては、[こちら](https://note.com/crefil/n/n7a0d2dec929b)を参照。 | ||
| - 値渡し | ||
| - 呼び出し先で再代入した場合 | ||
| - 呼び出し元に影響なし | ||
| - 呼び出し先でオブジェクトの操作をした場合 | ||
| - 呼び出し元に影響なし | ||
| - 参照渡し | ||
| - 呼び出し先で再代入した場合 | ||
| - 呼び出し元変数の参照先も変わる | ||
| - 呼び出し先でオブジェクトの操作をした場合 | ||
| - 呼び出し元変数が参照しているオブジェクトも変わる | ||
| - 参照の値渡し | ||
| - 呼び出し先で再代入した場合 | ||
| - 呼び出し元変数の参照先は変わらず、影響を受けなくなる | ||
| - 呼び出し先でオブジェクトの操作をした場合 | ||
| - 呼び出し元変数が参照しているオブジェクトも変わる | ||
| - backtrackingはコードが簡潔だが、可読性が低くなることがあると感じた | ||
|
|
||
| - 時間計算量:`O(n * 2^n)` | ||
| - 空間計算量:`O(n * 2^n)` | ||
| [こちら](https://www.youtube.com/watch?v=3JWtSMlq0Sw)の動画を参考にした。 | ||
|
|
||
| # 別解2. 再帰的な方法 | ||
| - (backtrackingとは異なる方法で)再帰的に部分集合を生成する方法。 | ||
| - `subsets(nums: list[int])`関数を用いて再帰を回す | ||
| - `subsets(nums[1:])`に`nums[0]`を含める場合と含めない場合の2通りを考えて、全ての部分集合を生成する。 | ||
| - 時間計算量:`O(n * 2^n)` | ||
| - 空間計算量:`O(n * 2^n)` | ||
|
|
||
|
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. 特に問題ないです。このあたりからいくつか別解を見てもいいかもしれません。 |
||
| # 別解3. スタックを用いた反復的な方法 | ||
|
|
||
| - スタックを用いて反復的に部分集合を生成する方法。 | ||
| - スタックに途中状態の部分集合と、走査中のインデックスのタプルを格納し、各部分集合に対して要素を含める場合と含めない場合の2通りを考えて、全ての部分集合を生成する。スタックの定義が非直感的で思いつくのが難しいと感じた。 | ||
|
|
||
| `stack.py` | ||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| all_subsets = [] | ||
| stack = [([], 0)] | ||
| while stack: | ||
| subset, index = stack.pop() | ||
| if index == len(nums): | ||
| all_subsets.append(subset[:]) | ||
| continue | ||
| stack.append((subset, index + 1)) | ||
| stack.append((subset + [nums[index]], index + 1)) | ||
| return all_subsets | ||
| ``` | ||
|
|
||
| - スタックに格納する部分集合`subset`は、新しい要素を追加する際に`subset + [nums[index]]`のように新しいリストを生成している。これにより、リストのコピーを作成して、元のリストを変更しないようにする。以下のコードでもこの仕様は確認できる。 | ||
| ```python | ||
| a = [1,2,3] | ||
| b = a + [4] # bはaのコピーに4を追加した新しいリスト | ||
| c = a # cはaの参照を受け取る | ||
| print(id(a) == id(b)) # False | ||
| print(id(a) == id(c)) # True | ||
| ``` | ||
|
|
||
| # 別解4. `extend`を用いた方法 | ||
| - `nums`の各要素`num`に対して、既存の部分集合にその要素を追加した新しい部分集合を生成し、全ての部分集合を構築する方法。イメージは倍々ゲームのように、部分集合の数が倍々に増えていく。最も実装がシンプル。 | ||
|
|
||
| `double.py` | ||
| ```python | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| all_subsets = [[]] | ||
| for num in nums: | ||
| subsets_containing_num = [subset + [num] for subset in all_subsets] | ||
| all_subsets.extend(subsets_containing_num) | ||
| return all_subsets | ||
| ``` | ||
|
|
||
|
|
||
| # 次に解く問題の予告 | ||
| - [Evaluate Division - LeetCode](https://leetcode.com/problems/evaluate-division/description/) | ||
| - [Subsets II - LeetCode](https://leetcode.com/problems/subsets-ii/description/) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| all_subsets = [[]] | ||
| for num in nums: | ||
| subsets_containing_num = [subset + [num] for subset in all_subsets] | ||
| all_subsets.extend(subsets_containing_num) | ||
| return all_subsets | ||
|
|
||
| # @lc code=end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| all_subsets = [] | ||
| stack = [([], 0)] | ||
| while stack: | ||
| subset, index = stack.pop() | ||
| if index == len(nums): | ||
| all_subsets.append(subset[:]) | ||
| continue | ||
| stack.append((subset, index + 1)) | ||
| stack.append((subset + [nums[index]], index + 1)) | ||
| return all_subsets | ||
|
|
||
| # @lc code=end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| all_subsets = [] | ||
| self.backtrack(nums, nums, [], all_subsets) | ||
| return all_subsets | ||
|
|
||
| def backtrack( | ||
| self, | ||
| nums: list[int], | ||
| remaining: list[int], | ||
| chosen: list[int], | ||
| all_subsets: list[list[int]], | ||
| ) -> None: | ||
| """ | ||
| the function backtrack count subset and add found subset to all_subsets. To count subset, backtrack choose an element num from remaining list for each step, and consider two patterns: | ||
| 1. the subset containing num | ||
| 2. the subset not containing num | ||
|
|
||
| backtrack([1,2,3], [1,2,3], [], all) | ||
| -> backtrack([1,2,3], [2,3], [1], all), backtrack([1,2,3], [2,3], [], all) | ||
|
|
||
| backtrack([1,2,3], [2,3], [1], all) | ||
| -> backtrack([1,2,3], [3], [2, 1], all), backtrack([1,2,3], [3], [1], all), ...etc | ||
| """ | ||
|
|
||
| if not remaining: | ||
| all_subsets.append(chosen) | ||
| return | ||
| num = remaining[0] | ||
|
|
||
| self.backtrack(nums, remaining[1:], chosen.copy(), all_subsets) | ||
| chosen.append(num) | ||
| self.backtrack(nums, remaining[1:], chosen.copy(), all_subsets) | ||
|
|
||
|
|
||
| # @lc code=end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| all_subsets: list[list[int]] = [] | ||
| num_subsets = pow(2, len(nums)) | ||
| for subset_expression in range(num_subsets): | ||
| subset: list[int] = [] | ||
| for i, num in enumerate(nums): | ||
| # i th digit of subset_expression represents if nums[i] is inclued in subset or not | ||
| include_i = (subset_expression >> i) & 1 | ||
| if include_i: | ||
| subset.append(num) | ||
| all_subsets.append(subset) | ||
| return all_subsets | ||
|
|
||
|
|
||
| # @lc code=end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| if not nums: | ||
| return [] | ||
| elif len(nums) == 1: | ||
| return [[], nums] | ||
| num = nums[0] | ||
| sub_subsets = self.subsets(nums[1:]) | ||
| all_subsets = [] | ||
| for subset in sub_subsets: | ||
| all_subsets.append(subset.copy()) | ||
| subset.append(num) | ||
| all_subsets.append(subset.copy()) | ||
| return all_subsets | ||
|
|
||
|
|
||
| # @lc code=end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| all_subsets: list[list[int]] = [] | ||
| subset = [] | ||
|
|
||
| def create_subset(i: int) -> None: | ||
| if i == len(nums): | ||
| all_subsets.append(subset[:]) | ||
| return | ||
| subset.append(nums[i]) | ||
| create_subset(i + 1) | ||
| subset.pop() | ||
| create_subset(i + 1) | ||
|
|
||
| create_subset(0) | ||
| return all_subsets | ||
|
|
||
|
|
||
| # @lc code=end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| # TC: O(n*2^n) | ||
| # SC: O(n*2^n) | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| all_subsets: list[list[int]] = [] | ||
| num_subsets = pow(2, len(nums)) | ||
| for subset_expression in range(num_subsets): | ||
| subset: list[int] = [] | ||
| for i, num in enumerate(nums): | ||
| # i th digit of subset_expression represents if nums[i] is inclued in subset or not | ||
| is_num_selected = self._ith_binary_digit(subset_expression, i) | ||
| if is_num_selected: | ||
| subset.append(num) | ||
| all_subsets.append(subset) | ||
| return all_subsets | ||
|
|
||
| def _ith_binary_digit(self, num: int, i: int) -> int: | ||
| return (num >> i) & 1 | ||
|
|
||
|
|
||
| # @lc code=end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| if not nums: | ||
| return [] | ||
| elif len(nums) == 1: | ||
| return [[], nums] | ||
| first_num = nums[0] | ||
| subsets_without_first = self.subsets(nums[1:]) | ||
| all_subsets = [] | ||
| for subset in subsets_without_first: | ||
| all_subsets.append(subset.copy()) | ||
| subset.append(first_num) | ||
| all_subsets.append(subset.copy()) | ||
| return all_subsets | ||
|
|
||
|
|
||
| # @lc code=end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| all_subsets = [] | ||
|
|
||
| subset = [] | ||
|
|
||
| # insert every possible subset to all_subsets | ||
| def backtrack(i: int) -> None: | ||
| if i == len(nums): | ||
| all_subsets.append(subset.copy()) | ||
| return | ||
| # case 1. nums[i] is selected | ||
| subset.append(nums[i]) | ||
| backtrack(i + 1) | ||
| # case 2. nums[i] is not selected | ||
| subset.pop() | ||
| backtrack(i + 1) | ||
|
|
||
| backtrack(0) | ||
| return all_subsets | ||
|
|
||
|
|
||
| # @lc code=end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # | ||
| # @lc app=leetcode id=78 lang=python3 | ||
| # | ||
| # [78] Subsets | ||
| # | ||
|
|
||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def subsets(self, nums: list[int]) -> list[list[int]]: | ||
| num_all_subsets = pow(2, len(nums)) | ||
| all_subsets: list[list[int]] = [] | ||
|
|
||
| for subset_binary_expression in range(num_all_subsets): | ||
| subset: list[int] = [] | ||
| for i, num in enumerate(nums): | ||
| is_num_selected = self._digit_i(subset_binary_expression, i) | ||
| if is_num_selected: | ||
| subset.append(num) | ||
| all_subsets.append(subset) | ||
|
|
||
| return all_subsets | ||
|
|
||
| def _digit_i(self, num: int, i: int) -> int: | ||
| """ | ||
| _digit_i returns i th binary digit value of num. | ||
| e.g. _digit_i(4,0) = _digit_i(4,1) = 0, _digit_i(4,2) = 1 | ||
| """ | ||
|
|
||
| return (num >> i) & 1 | ||
|
|
||
|
|
||
| # @lc code=end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
スライスを使うとコピーが発生し、O(n)かかるので、indexで範囲指定をしたほうが良いかもしれません。
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.
確かにおっしゃる通りですね、ご指摘ありがとうございます。