Skip to content

Conversation

@Kaichi-Irie
Copy link
Owner

問題へのリンク

Subsets - LeetCode

言語

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)
    こちらの動画を参考にした。

別解2. 再帰的な方法

  • (backtrackingとは異なる方法で)再帰的に部分集合を生成する方法。
  • subsets(nums: list[int])関数を用いて再帰を回す
  • subsets(nums[1:])nums[0]を含める場合と含めない場合の2通りを考えて、全ての部分集合を生成する。
  • 時間計算量:O(n * 2^n)
  • 空間計算量:O(n * 2^n)

次に解く問題の予告

- `_ith_binary_digit`関数を定義して、整数をビットマスクとして解釈する際に、`i`番目のビットが立っているかどうかを判定する処理を分離した。

# 別解1. backtracking
- backtrackingを用いて部分集合を生成する方法。再帰的に要素を選択するかどうかを決定し、部分集合を構築していく。`subset`変数は参照渡しで更新され、最後に`all_subsets`に追加するときにコピーされる。そのため、更新の順序に注意が必要。
Copy link

Choose a reason for hiding this comment

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

https://docs.python.org/ja/3.7/faq/programming.html#id18
言いたいことはわかりますが、Pythonに参照渡しはないです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ご指摘ありがとうございます。「参照渡し」という言葉をもっぱら「ポインタ渡し(参照の値渡し)」のことかと思っておりました。
そして「参照の参照渡し」(�Python公式ドキュメントの参照渡し)はPythonでは実装されていないのですね。

Copy link

Choose a reason for hiding this comment

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

Copy link
Owner Author

@Kaichi-Irie Kaichi-Irie Jun 19, 2025

Choose a reason for hiding this comment

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

こちらもかなり参考になると感じました。
【Python】Python に参照渡しは存在しない話|CREFIL

  • 値渡し
    • 呼び出し先で再代入した場合
      • 呼び出し元に影響なし
    • 呼び出し先でオブジェクトの操作をした場合
      • 呼び出し元に影響なし
  • 参照渡し
    • 呼び出し先で再代入した場合
      • 呼び出し元変数の参照先も変わる
    • 呼び出し先でオブジェクトの操作をした場合
      • 呼び出し元変数が参照しているオブジェクトも変わる
  • 参照の値渡し
    • 呼び出し先で再代入した場合
      • 呼び出し元変数の参照先は変わらず、影響を受けなくなる
    • 呼び出し先でオブジェクトの操作をした場合
      • 呼び出し元変数が参照しているオブジェクトも変わる

Copy link
Owner Author

@Kaichi-Irie Kaichi-Irie Jun 19, 2025

Choose a reason for hiding this comment

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

代入のたびにメモリの番地が変わる仕様であるというのも知りませんでした。

なお、小さい値( 1,2,3など)は、よく使われるからためプリロードされているようです。(https://medium.com/techtofreedom/10-python-interview-questions-for-senior-developers-4fefe773719a)

# Pythonでは代入のたびにメモリの場所が変わる
# 代入は値の評価、値のメモリ確保→変数へのバインドがなされるらしい

x = 100000
print(id(x))  # 4566145616
x = 100000
print(id(x))  # 4566148464
x = 100000
print(id(x))  # 4566148784

# 小さい値のプリロード
x = 1
print(id(x))  # 4393378672
x = 1
print(id(x))  # 4393378672

# 別解2. 再帰的な方法
- (backtrackingとは異なる方法で)再帰的に部分集合を生成する方法。
- `subsets(nums: list[int])`関数を用いて再帰を回す
- `subsets(nums[1:])`に`nums[0]`を含める場合と含めない場合の2通りを考えて、全ての部分集合を生成する。
Copy link

Choose a reason for hiding this comment

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

スライスを使うとコピーが発生し、O(n)かかるので、indexで範囲指定をしたほうが良いかもしれません。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かにおっしゃる通りですね、ご指摘ありがとうございます。

- `subsets(nums[1:])`に`nums[0]`を含める場合と含めない場合の2通りを考えて、全ての部分集合を生成する。
- 時間計算量:`O(n * 2^n)`
- 空間計算量:`O(n * 2^n)`

Copy link

Choose a reason for hiding this comment

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

特に問題ないです。このあたりからいくつか別解を見てもいいかもしれません。
https://github.com/olsen-blue/Arai60/pull/52/files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants