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
32 changes: 32 additions & 0 deletions 46/step1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# step1 何も見ずに解く
[1,2,3]の場合、
[[]]
[[1], [2], [3]]
[[1, 2], [1, 3], [2, 3],[2, 1], [3, 1], [3, 2]]
といった形で増やしていけば良さそう。
つまり、配列の各permutationにまだ追加していない値があれば追加したものを加えるというループを繰り返してpermutationの長さがnumsのサイズになれば終了すれば良い。

```ruby
# @param {Integer[]} nums
# @return {Integer[][]}
def permute(nums)
permutations = [[]]
while permutations.first.size < nums.size
next_permutations = []
permutations.each do |permutation|
next_permutations.concat((nums - permutation).map { |num| (permutation + [num]) })
end
permutations = next_permutations
end
permutations
end
```

ある時点でのpermutationの長さをkとすると、
```
(nums - permutation).map { |num| (permutation + [num]) }
```
の部分で(n - k) * kの計算量がかかる。
これを各階層のノードの数とかけて0 - nのkについて合計すると、計算量はO(n * n!)になる。
空間計算量は最終的に作られるpermutationの数 * 各permutationの長さなのでO(n * n!)
nの最大値が6なので1秒以内で間に合うが、少しでも大きくなったらどうしようもなさそう。
58 changes: 58 additions & 0 deletions 46/step2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# step2 他の方の解答を見る

## 使用中のnumsをSetにいれるかどうか
https://github.com/Ryotaro25/leetcode_first60/pull/54#discussion_r1986035628

numsの中でpermutationで使われていないものを取り出す時に線形に検索するとnが乗ってしまう。
これは(Intersection of Two Arrays)[https://leetcode.com/problems/intersection-of-two-arrays/editorial/]と同じ話かと思った。

https://github.com/tokuhirat/LeetCode/pull/50/files#r2278840164
一方で、問題の制限はnが6以下であるし、オーバーヘッドを加味すると配列のまま計算する方が速いかもしれない。
ハッシュテーブルにした方が速くなるくらいのサイズだとそもそも現実的な計算時間で終わらなくなっている気もするな…

と思ってRubyのコードを読んだところ、特定の長さの配列まではハッシュテーブルを使わずに線形探索していた。
そういえば積集合をとる`&`も同じことをしていたんだった。
ref: https://github.com/ruby/ruby/blob/5257e1298c4dc4e854eaa0a9fe5e6dc5c1495c91/array.c#L5558-L5583

## 再帰で解く
https://github.com/ryosuketc/leetcode_arai60/pull/39

```ruby
# @param {Integer[]} nums
# @return {Integer[][]}
def permute(nums)
permute_helper = lambda do |nums|
permutes = []
append_permutes = lambda do |permute, nums|
if nums.empty?
permutes << permute
return
end
nums.each_with_index do |num, i|
new_permute = permute + [num]
new_nums = nums[0...i] + nums[(i + 1)..-1]
append_permutes.call(new_permute, new_nums)
end
end
append_permutes.call([], nums)
permutes
end
permute_helper.call(nums)
end
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

はじめに読んだ時、lambda がネストされていて難しく感じました。nums はブロックパラメータに書かなくても読めるはずなので書かれているのはなんでだろうと思いました。処理を読むと append_permutes の nums はまだ追加していない nums であり、ブロックパラメータに書く必要があるとわかりました。また、permute_helper は入出力が permute と同じなのでまとめられないかなと思いました。自分だったら以下のように書くと思います。

def permute(nums)
    results = []
    permute_helper = lambda do |permuted_prefix, remaining_nums|
        if remaining_nums.empty?
            results << permuted_prefix
            return
        end
        remaining_nums.each_with_index do |num, i|
            new_permuted_prefix = permuted_prefix + [num]
            new_remaining_nums = remaining_nums[0...i] + remaining_nums[(i + 1)..-1]
            permute_helper.call(new_permuted_prefix, new_remaining_nums)
        end
    end
    permute_helper.call([], nums)
    results
end

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.

コメントありがとうございます。
おっしゃる通りlambdaの引数として利用しているnumsが関数の引数の名前と同じでかなり読みにくいですね。
あと外側のlambdaには引数は不要ですね。

個人的にはresultsをlambdaの外のものを変更するのがあまり好きではないので以下のように書こうと思います。

# @param {Integer[]} nums
# @return {Integer[][]}
def permute(nums)
    permute_helper = lambda do
        permutes = []
        append_permutes = lambda do |permute, remaining_nums|
            if remaining_nums.empty?
                permutes << permute 
                return
            end
            remaining_nums.each_with_index do |num, i|
                new_permute = permute + [num]
                new_nums = remaining_nums[0...i] + remaining_nums[(i + 1)..-1]
                append_permutes.call(new_permute, new_nums)
            end
        end
        append_permutes.call([], nums)
        permutes
    end
    permute_helper.call
end

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.

少し考えたのですが、この程度であればlambdaはネストさせなくても良い気がしてきました。
permute内に処理が追加されるようなら検討しても良いかもしれません。(その時はlamdaをネストさせるより別のメソッドに切り出すかもしれないですが)


## step1の改善

```ruby
# @param {Integer[]} nums
# @return {Integer[][]}
def permute(nums)
permutes = [[]]
while permutes.first.size != nums.size
permutes = permutes.each_with_object([]) do |permute, next_permutes|
next_permutes.concat((nums - permute).map { |num| permute + [num] })
end
end
permutes
end
```
15 changes: 15 additions & 0 deletions 46/step3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# step3 3回続けて10分以内に書いてエラーを出さなければOKとする

```ruby
# @param {Integer[]} nums
# @return {Integer[][]}
def permute(nums)
permutes = [[]]
while permutes.first.size != nums.size
permutes = permutes.each_with_object([]) do |permute, next_permutes|

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

細かいですが、permuteは動詞の印象があるので、この辺の変数の名前はpermutationsやpermsにしたいかもしれません。

next_permutes.concat((nums - permute).map { |num| permute + [num] })

@potrue potrue Oct 5, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

こちらのやり方はこちらのやり方で読みやすいと思いますが、割と広く知られている手法としては、次のようなバックトラッキングを用いたやり方で順列を生成するアルゴリズムがあると思います。
(LLMに書かせたコードなので読みにくかったらすみません)

def permute_inplace(nums)
  res = []

  backtrack = ->(first) do
    if first == nums.length
      res << nums.dup
      return
    end

    (first...nums.length).each do |i|
      nums[first], nums[i] = nums[i], nums[first]
      backtrack.call(first + 1)
      nums[first], nums[i] = nums[i], nums[first]  # 元に戻す
    end
  end

  backtrack.call(0)
  res
end

どこまで作ったかのindexを渡すことで、途中まで作った順列を表す配列それ自体にnums - permuteを含めることができるというのと、各要素の各段階において新しい配列を作る必要がない、という部分で多少効率的かと思います。

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.

ありがとうございます!
発想にまだ慣れていないですがコード自体は読みやすいです。

end
end
permutes
end
```
1 change: 1 addition & 0 deletions 46/step4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## step4 レビューを受けて解答を修正