From ca705ea623c30fa758e41fb784076bda8d1d28e8 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Wed, 18 Jun 2025 18:29:36 +0900 Subject: [PATCH 1/6] Solve 78_subsets_medium --- 78_subsets_medium/README.md | 37 ++++++++++++++++++++ 78_subsets_medium/step1_backtrack.py | 44 ++++++++++++++++++++++++ 78_subsets_medium/step1_bit_operation.py | 24 +++++++++++++ 78_subsets_medium/step1_recursive.py | 25 ++++++++++++++ 78_subsets_medium/step2_backtrack.py | 27 +++++++++++++++ 78_subsets_medium/step2_bit_operation.py | 29 ++++++++++++++++ 78_subsets_medium/step2_recursive.py | 25 ++++++++++++++ 78_subsets_medium/step3.py | 31 +++++++++++++++++ 78_subsets_medium/step3_bit_operation.py | 33 ++++++++++++++++++ 9 files changed, 275 insertions(+) create mode 100644 78_subsets_medium/README.md create mode 100644 78_subsets_medium/step1_backtrack.py create mode 100644 78_subsets_medium/step1_bit_operation.py create mode 100644 78_subsets_medium/step1_recursive.py create mode 100644 78_subsets_medium/step2_backtrack.py create mode 100644 78_subsets_medium/step2_bit_operation.py create mode 100644 78_subsets_medium/step2_recursive.py create mode 100644 78_subsets_medium/step3.py create mode 100644 78_subsets_medium/step3_bit_operation.py diff --git a/78_subsets_medium/README.md b/78_subsets_medium/README.md new file mode 100644 index 0000000..7f64201 --- /dev/null +++ b/78_subsets_medium/README.md @@ -0,0 +1,37 @@ +# 問題へのリンク +[Subsets - LeetCode](https://leetcode.com/problems/subsets/description/) + + +# 言語 +Python + +# 問題の概要 +与えられた整数のリストから、すべての部分集合を生成する問題。 + + +# 自分の解法 bit演算を用いて解く +全ての部分集合は、`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`に追加するときにコピーされる。そのため、更新の順序に注意が必要。 +- 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)` + +# 次に解く問題の予告 +- [Evaluate Division - LeetCode](https://leetcode.com/problems/evaluate-division/description/)f diff --git a/78_subsets_medium/step1_backtrack.py b/78_subsets_medium/step1_backtrack.py new file mode 100644 index 0000000..2d6980f --- /dev/null +++ b/78_subsets_medium/step1_backtrack.py @@ -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 diff --git a/78_subsets_medium/step1_bit_operation.py b/78_subsets_medium/step1_bit_operation.py new file mode 100644 index 0000000..48a9810 --- /dev/null +++ b/78_subsets_medium/step1_bit_operation.py @@ -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 diff --git a/78_subsets_medium/step1_recursive.py b/78_subsets_medium/step1_recursive.py new file mode 100644 index 0000000..853acd2 --- /dev/null +++ b/78_subsets_medium/step1_recursive.py @@ -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 diff --git a/78_subsets_medium/step2_backtrack.py b/78_subsets_medium/step2_backtrack.py new file mode 100644 index 0000000..01fb030 --- /dev/null +++ b/78_subsets_medium/step2_backtrack.py @@ -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 diff --git a/78_subsets_medium/step2_bit_operation.py b/78_subsets_medium/step2_bit_operation.py new file mode 100644 index 0000000..9d97e58 --- /dev/null +++ b/78_subsets_medium/step2_bit_operation.py @@ -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 diff --git a/78_subsets_medium/step2_recursive.py b/78_subsets_medium/step2_recursive.py new file mode 100644 index 0000000..a8fe05e --- /dev/null +++ b/78_subsets_medium/step2_recursive.py @@ -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 diff --git a/78_subsets_medium/step3.py b/78_subsets_medium/step3.py new file mode 100644 index 0000000..205deba --- /dev/null +++ b/78_subsets_medium/step3.py @@ -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 diff --git a/78_subsets_medium/step3_bit_operation.py b/78_subsets_medium/step3_bit_operation.py new file mode 100644 index 0000000..a3776b0 --- /dev/null +++ b/78_subsets_medium/step3_bit_operation.py @@ -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 From da534d94b171ea4aa4a9c2cfdb65757f3c1d4caf Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Wed, 18 Jun 2025 18:31:27 +0900 Subject: [PATCH 2/6] Fix formatting in README and add link for Subsets II problem --- 78_subsets_medium/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/78_subsets_medium/README.md b/78_subsets_medium/README.md index 7f64201..6ddc917 100644 --- a/78_subsets_medium/README.md +++ b/78_subsets_medium/README.md @@ -34,4 +34,5 @@ Python - 空間計算量:`O(n * 2^n)` # 次に解く問題の予告 -- [Evaluate Division - LeetCode](https://leetcode.com/problems/evaluate-division/description/)f +- [Evaluate Division - LeetCode](https://leetcode.com/problems/evaluate-division/description/) +- [Subsets II - LeetCode](https://leetcode.com/problems/subsets-ii/description/) From 9e317fdfa5ce183e0c7b0f5d2f5a1d38c1f5bd07 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Thu, 19 Jun 2025 17:32:27 +0900 Subject: [PATCH 3/6] Implement recursive solution for generating subsets in 78_subsets_medium --- 78_subsets_medium/step3_recursive.py | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 78_subsets_medium/step3_recursive.py diff --git a/78_subsets_medium/step3_recursive.py b/78_subsets_medium/step3_recursive.py new file mode 100644 index 0000000..2c8cceb --- /dev/null +++ b/78_subsets_medium/step3_recursive.py @@ -0,0 +1,37 @@ +# +# @lc app=leetcode id=78 lang=python3 +# +# [78] Subsets +# + + +# @lc code=start +class Solution: + def subsets(self, nums: list[int]) -> list[list[int]]: + def generate_subsets_before_index(index: int) -> list[list[int]]: + """ + generate_subsets_before_index(index) generates subsets of list nums[:index] + """ + if index == 0: + return [] + if index == 1: + return [[], [nums[0]]] + elif index > len(nums): + index = len(nums) + + all_subsets: list[list[int]] = [] + # sub_subsets is a list of all subsets of nums[0:index-1] + sub_subsets: list[list[int]] = generate_subsets_before_index(index - 1) + last_num = nums[index - 1] + + for subset in sub_subsets: + all_subsets.append(subset.copy()) + subset.append(last_num) + all_subsets.append(subset.copy()) + + return all_subsets + + return generate_subsets_before_index(len(nums)) + + +# @lc code=end From 0af1c4d52546d91ea53ec788d5c81f11a3e73019 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Thu, 19 Jun 2025 18:22:52 +0900 Subject: [PATCH 4/6] Update README to clarify backtracking explanation and add details on Python's parameter passing --- 78_subsets_medium/README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/78_subsets_medium/README.md b/78_subsets_medium/README.md index 6ddc917..6cc7a19 100644 --- a/78_subsets_medium/README.md +++ b/78_subsets_medium/README.md @@ -19,7 +19,23 @@ Python - `_ith_binary_digit`関数を定義して、整数をビットマスクとして解釈する際に、`i`番目のビットが立っているかどうかを判定する処理を分離した。 # 別解1. backtracking -- backtrackingを用いて部分集合を生成する方法。再帰的に要素を選択するかどうかを決定し、部分集合を構築していく。`subset`変数は参照渡しで更新され、最後に`all_subsets`に追加するときにコピーされる。そのため、更新の順序に注意が必要。 +- backtrackingを用いて部分集合を生成する方法。再帰的に要素を選択するかどうかを決定し、部分集合を構築していく。`subset`リストはオブジェクトの操作で更新され、最後に`all_subsets`に追加するときにコピーされる。そのため、思わぬところに影響が出ないように更新の順序に注意が必要。 + - なお、Pythonの値渡し、参照渡し、参照の値渡しについては、[こちら](https://note.com/crefil/n/n7a0d2dec929b)を参照。 + - 値渡し + - 呼び出し先で再代入した場合 + - 呼び出し元に影響なし + - 呼び出し先でオブジェクトの操作をした場合 + - 呼び出し元に影響なし + - 参照渡し + - 呼び出し先で再代入した場合 + - 呼び出し元変数の参照先も変わる + - 呼び出し先でオブジェクトの操作をした場合 + - 呼び出し元変数が参照しているオブジェクトも変わる + - 参照の値渡し + - 呼び出し先で再代入した場合 + - 呼び出し元変数の参照先は変わらず、影響を受けなくなる + - 呼び出し先でオブジェクトの操作をした場合 + - 呼び出し元変数が参照しているオブジェクトも変わる - backtrackingはコードが簡潔だが、可読性が低くなることがあると感じた - 時間計算量:`O(n * 2^n)` From 646059b80c04a3dd09287611c3bfda63569e484e Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 10 Nov 2025 07:34:34 +0900 Subject: [PATCH 5/6] Update README and add new solutions for generating subsets using stack and extend methods --- 78_subsets_medium/README.md | 39 ++++++++++++++++++++++++++++++++++++- 78_subsets_medium/double.py | 16 +++++++++++++++ 78_subsets_medium/stack.py | 21 ++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 78_subsets_medium/double.py create mode 100644 78_subsets_medium/stack.py diff --git a/78_subsets_medium/README.md b/78_subsets_medium/README.md index 6cc7a19..5d3ed73 100644 --- a/78_subsets_medium/README.md +++ b/78_subsets_medium/README.md @@ -9,7 +9,8 @@ Python 与えられた整数のリストから、すべての部分集合を生成する問題。 -# 自分の解法 bit演算を用いて解く +# 自分の解法 +## step1:ビット全探索 全ての部分集合は、`nums`の要素数を`n`としたとき、`2^n`通り存在する。各部分集合は、`0`から`2^n - 1`までの整数をビットマスクとして解釈することで生成できる。 - 時間計算量:`O(n * 2^n)` @@ -49,6 +50,42 @@ Python - 時間計算量:`O(n * 2^n)` - 空間計算量:`O(n * 2^n)` +# 別解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 +``` + +# 別解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/) diff --git a/78_subsets_medium/double.py b/78_subsets_medium/double.py new file mode 100644 index 0000000..c6ba7d3 --- /dev/null +++ b/78_subsets_medium/double.py @@ -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 diff --git a/78_subsets_medium/stack.py b/78_subsets_medium/stack.py new file mode 100644 index 0000000..a475281 --- /dev/null +++ b/78_subsets_medium/stack.py @@ -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 From efc367efb5a232d9fdbe0e03f435183dd12f3070 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 10 Nov 2025 07:36:41 +0900 Subject: [PATCH 6/6] Update README --- 78_subsets_medium/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/78_subsets_medium/README.md b/78_subsets_medium/README.md index 5d3ed73..9c902e1 100644 --- a/78_subsets_medium/README.md +++ b/78_subsets_medium/README.md @@ -71,6 +71,15 @@ class Solution: 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`に対して、既存の部分集合にその要素を追加した新しい部分集合を生成し、全ての部分集合を構築する方法。イメージは倍々ゲームのように、部分集合の数が倍々に増えていく。最も実装がシンプル。