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
170 changes: 170 additions & 0 deletions src/bin/step1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Step1
// 目的: 方法を思いつく

// 方法
// 5分考えてわからなかったら答えをみる
// 答えを見て理解したと思ったら全部消して答えを隠して書く
// 5分筆が止まったらもう一回みて全部消す
// 正解したら終わり

/*
問題の理解
- 整数からなる配列numsが与えられる。numsから作成することが可能なサブセットを生成して返す。
nums=[1,2,3]
subsets=[[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]
サブセットとは値に重複のない全ての可能な組み合わせだと理解した。

何を考えて解いていたか
- HashSetにおいて並び順が異なり同じ値を持つ配列が重複として扱われないので、HashSetに重複管理させるのは無理。
- 毎回ソートするのも筋が悪そう。
- ループを回しながら重複しない配列のを作っていく。
- 空の配列とnums,nums[i]を解の配列に詰める。O(n)
再帰処理
- base_case
nums.len() == 1でreturn
- recursive_case
num = nums.pop_front()
numsを解の配列に詰める
nums.push_back(num)

時間計算量 O(n!)
空間計算量 O(n!)
問題の制約から nums.length <= 10 となるので階乗の計算量でも問題ないと判断。
そもそも、要求される解が可能な組み合わせを列挙するものなので、時間計算量はあまり改善できなず定数因数のみの改善しか行えないと思った。
途中でswapの方法でも書けると思ったが、まずは自然に思いついたVecDequeのpop_front(),push_back()を利用した解法で実装する。
重複する配列が出力され、Wrong Answerとなった。
重複する配列を含まずに実装する方法がすぐに思いつかないので、配列をsortしてHashSetにinsertすることで重複排除を行う。
sortによって時間計算量が O(n! * n log n)になると考える。
10! * 10 log 10 = 36,288,000 となる。秒あたり10億ステップだと見積もると 36,288,000 / 10 ^ 8 = 0.36288 = 約362msとなり、現実的な実行時間ではある。
Acceptedになった(Runtime 288ms)。しかし、他の解法ではRuntime 0msのようなので明らかにソートせずに実装する解法があることが分かる。
step2で解法を見る。

何がわからなかったか
- 重複する配列を結果に含めずに処理するアルゴリズム

正解してから気づいたこと
- 時間計算量の見積もりが不安だったのでGPT-5.2に聞いたところ正確にはsortの時間計算量よりも、make_subsets内のforループでcloneしているコストの方が大きくO(n! * n ^ 2)になるとのことだった。
Copy link

Choose a reason for hiding this comment

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

nが10で log n と n では4倍くらいしか違わないはずです。

が、GPT-5.2 のその計算量評価は間違いでしょう。

f(n) = cost(sort(n)) + n * (cost(clone(n-1)) + f(n - 1)) となっているので、sort と clone 部分は最後定数になるはずです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。
f(n) = cost(sort(n)) + n * (cost(clone(n-1)) + f(n - 1)) のとき、n * f(n - 1)の部分が支配的であるので時間計算量はO(n!)になると理解しました。

Copy link

Choose a reason for hiding this comment

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

そうですね。具体的に代入してみれば明らかに感じるでしょう。

なので、実行時間の概算がかなり近い数値で見積もれたのはたまたまかと思った。

所感
- Wrong Answerになった時点で解答を見ようかと思ったが、時間計算量は悪化することが分かったうえでsortとHashSetによる重複排除による解法を実装してみることにしたのは良かったと思った。
Acceptedになったから良かったということではなくて、最適解のアルゴリズムではないからナイーブな実装を試すことすらしないのは良くない癖だというコメントを思い出してこれを実践できたため。
答えがわからないので腕を組んで時間を浪費するのは良くないが、ナイーブな実装がわかっているならまずは実装してみて、そこを起点にアルゴリズムを改善すれば良いという考え方。
*/

use std::collections::{HashSet, VecDeque};

pub struct Solution {}
impl Solution {
pub fn subsets(nums: Vec<i32>) -> Vec<Vec<i32>> {
if nums.is_empty() {
return vec![vec![]];
}

let mut subsets = HashSet::new();

subsets.insert(vec![]);
for i in 0..nums.len() {
subsets.insert(vec![nums[i]]);
}

Self::make_subsets(nums, &mut subsets);

subsets.into_iter().fold(vec![], |mut result, subset| {
result.push(subset);
result
})
}

fn make_subsets(nums: Vec<i32>, subsets: &mut HashSet<Vec<i32>>) {
if nums.len() == 1 {
return;
}

let mut subset = nums.clone();
subset.sort();
subsets.insert(subset);

let mut nums = VecDeque::from_iter(nums.into_iter());
for _ in 0..nums.len() {
let num = nums.pop_front().unwrap();
Self::make_subsets(nums.clone().into(), subsets);
nums.push_back(num);
}
}
}

#[cfg(test)]
mod tests {
use std::collections::HashSet;

use super::*;

#[test]
fn playground() {
let mut set = HashSet::new();
set.insert(&[1, 2]);
set.insert(&[2, 1]);
assert_eq!(set.len(), 2);

let mut set = HashSet::new();
set.insert(&[2]);
set.insert(&[2]);
assert_eq!(set.len(), 1);
}

#[test]
fn step1_test() {
let mut subsets = Solution::subsets(vec![1, 2, 3]);
let mut expect = vec![
vec![],
vec![1],
vec![2],
vec![1, 2],
vec![3],
vec![1, 3],
vec![2, 3],
vec![1, 2, 3],
];
subsets.iter_mut().for_each(|x| x.sort());
subsets.sort();
expect.iter_mut().for_each(|x| x.sort());
expect.sort();
assert_eq!(subsets, expect);

let mut subsets = Solution::subsets(vec![3, 2, 4, 1]);
let mut expect = vec![
vec![],
vec![3],
vec![2],
vec![2, 3],
vec![4],
vec![3, 4],
vec![2, 4],
vec![2, 3, 4],
vec![1],
vec![1, 3],
vec![1, 2],
vec![1, 2, 3],
vec![1, 4],
vec![1, 3, 4],
vec![1, 2, 4],
vec![1, 2, 3, 4],
];
subsets.iter_mut().for_each(|x| x.sort());
subsets.sort();
expect.iter_mut().for_each(|x| x.sort());
expect.sort();
assert_eq!(subsets, expect);

let mut subsets = Solution::subsets(vec![0]);
let mut expect = vec![vec![], vec![0]];
subsets.iter_mut().for_each(|x| x.sort());
subsets.sort();
expect.iter_mut().for_each(|x| x.sort());
expect.sort();
assert_eq!(subsets, expect);

assert_eq!(Solution::subsets(vec![]), vec![vec![]]);
}
}
111 changes: 111 additions & 0 deletions src/bin/step2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Step2
// 目的: 自然な書き方を考えて整理する

// 方法
// Step1のコードを読みやすくしてみる
// 他の人のコードを2つは読んでみること
// 正解したら終わり

// 以下をメモに残すこと
// 講師陣はどのようなコメントを残すだろうか?
// 他の人のコードを読んで考えたこと
// 改善する時に考えたこと

/*
他の人のコードを読んで考えたこと
https://github.com/hayashi-ay/leetcode/pull/63/changes#r1537661488
- backtrackingの定義について。再帰の戻り掛けに何らかの処理を行うこと。
https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0

https://github.com/Yoshiki-Iwasa/Arai60/pull/56/changes#diff-1d48419b0e20772b019b29a3bf3ff9761657623bc3ae1335b2a2f41add6b19a8R8
- Rust実装のbacktracking

https://github.com/ryosuketc/leetcode_arai60/pull/40/changes#diff-ad01407803e073f539072a743ce608f45e09a7eb23b9d903e6b81ad196ea9c32R10
- bit全探索は知らないが、二通りの選択肢だと考えるとbit全探索が適用できるかもしれないという思考の流れになるのかと思った。

参考した解法の理解
https://github.com/Yoshiki-Iwasa/Arai60/pull/56/changes#diff-1d48419b0e20772b019b29a3bf3ff9761657623bc3ae1335b2a2f41add6b19a8R8
- make_subsetsのfor-loopの中で再帰処理をする直前でsubsetにpushして、その後pop()してもとに戻している部分がbacktracking

所感
- 答えのコードを見ると何をしているか、どのようにデータが遷移するかは理解できるものの、問題文からこの解法にたどり着くのには距離を感じる。
*/

pub struct Solution {}
impl Solution {
pub fn subsets(nums: Vec<i32>) -> Vec<Vec<i32>> {
Self::make_subsets(&nums, &mut Vec::new())
}

fn make_subsets(nums: &[i32], subset: &mut Vec<i32>) -> Vec<Vec<i32>> {
let mut all_subsets = Vec::new();
all_subsets.push(subset.to_vec());

for (i, num) in nums.iter().enumerate() {
subset.push(*num);
all_subsets.extend(Self::make_subsets(&nums[i + 1..], subset));
Copy link

Choose a reason for hiding this comment

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

ここ、extend で shallow copy (move)発生という理解でいいですか?

make_subsets にもう一つ引数を増やし、与えられた最後の引数に結果を extend する関数とするとこの問題は解決しますね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ここ、extend で shallow copy (move)発生という理解でいいですか?

はい。Self::make_subsetsの結果をmoveしています。


make_subsets にもう一つ引数を増やし、与えられた最後の引数に結果を extend する関数とするとこの問題は解決しますね。

副作用で引数に変更を加えていくと理解しました。

impl Solution {
    pub fn subsets(nums: Vec<i32>) -> Vec<Vec<i32>> {
        let mut all_subsets = Vec::new();
        Self::make_subsets(&nums, &mut Vec::new(), &mut all_subsets);

        all_subsets
    }

    fn make_subsets(nums: &[i32], subset: &mut Vec<i32>, all_subsets: &mut Vec<Vec<i32>>) {
        all_subsets.extend(vec![subset.to_vec()]);

        for (i, num) in nums.iter().enumerate() {
            subset.push(*num);
            Self::make_subsets(&nums[i + 1..], subset, all_subsets);
            subset.pop();
        }
    }
}

subset.pop();
}
all_subsets
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn step2_test() {
let mut subsets = Solution::subsets(vec![1, 2, 3]);
let mut expect = vec![
vec![],
vec![1],
vec![2],
vec![1, 2],
vec![3],
vec![1, 3],
vec![2, 3],
vec![1, 2, 3],
];
subsets.iter_mut().for_each(|x| x.sort());
subsets.sort();
expect.iter_mut().for_each(|x| x.sort());
expect.sort();
assert_eq!(subsets, expect);

let mut subsets = Solution::subsets(vec![3, 2, 4, 1]);
let mut expect = vec![
vec![],
vec![3],
vec![2],
vec![2, 3],
vec![4],
vec![3, 4],
vec![2, 4],
vec![2, 3, 4],
vec![1],
vec![1, 3],
vec![1, 2],
vec![1, 2, 3],
vec![1, 4],
vec![1, 3, 4],
vec![1, 2, 4],
vec![1, 2, 3, 4],
];
subsets.iter_mut().for_each(|x| x.sort());
subsets.sort();
expect.iter_mut().for_each(|x| x.sort());
expect.sort();
assert_eq!(subsets, expect);

let mut subsets = Solution::subsets(vec![0]);
let mut expect = vec![vec![], vec![0]];
subsets.iter_mut().for_each(|x| x.sort());
subsets.sort();
expect.iter_mut().for_each(|x| x.sort());
expect.sort();
assert_eq!(subsets, expect);

assert_eq!(Solution::subsets(vec![]), vec![vec![]]);
}
}
105 changes: 105 additions & 0 deletions src/bin/step2a.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Step2a
// 目的: 再帰処理をスタックの解法に書き換える練習を行う

// 方法
// Step1のコードを読みやすくしてみる
// 他の人のコードを2つは読んでみること
// 正解したら終わり

// 以下をメモに残すこと
// 講師陣はどのようなコメントを残すだろうか?
// 他の人のコードを読んで考えたこと
// 改善する時に考えたこと

/*
所感
https://github.com/Yoshiki-Iwasa/Arai60/pull/56/changes#r1741301219
> ちょっとよく分かっていないんですが、ここの clone は不要ですか?
> push pop で戻しているということは。
- ここのコメントは自分の書いたコードにも当てはまる気がするが、cloneは必要な気がする。
ある時点のsubsetにnums[i]をpushした状態は独立してall_subsetsに加える必要があるという理解のため。
GPT-5.2に聞いてみたところ、subsetをスタックに積む方法ではclone()は避けられなさそうだった。
処理を大幅に書き換えれば、for-loopの中のsubset.clone()はなくせそうだったが冗長すぎて書きたくないなと思った。
- step2ではsubset.clone()に当たる部分は可変参照で取り回しているのでclone()していないものの、all_subsetsを毎回確保しているのでコストは結局同じになっていると思った。
*/

pub struct Solution {}
impl Solution {
pub fn subsets(nums: Vec<i32>) -> Vec<Vec<i32>> {
let mut all_subsets = Vec::new();
let mut frontier = Vec::new();

frontier.push((nums.as_slice(), vec![]));
while let Some((nums, mut subset)) = frontier.pop() {
all_subsets.push(subset.clone());

for i in 0..nums.len() {
subset.push(nums[i]);
frontier.push((&nums[i + 1..], subset.clone()));
subset.pop();
}
}

all_subsets
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn step2a_test() {
let mut subsets = Solution::subsets(vec![1, 2, 3]);
let mut expect = vec![
vec![],
vec![1],
vec![2],
vec![1, 2],
vec![3],
vec![1, 3],
vec![2, 3],
vec![1, 2, 3],
];
subsets.iter_mut().for_each(|x| x.sort());
subsets.sort();
expect.iter_mut().for_each(|x| x.sort());
expect.sort();
assert_eq!(subsets, expect);

let mut subsets = Solution::subsets(vec![3, 2, 4, 1]);
let mut expect = vec![
vec![],
vec![3],
vec![2],
vec![2, 3],
vec![4],
vec![3, 4],
vec![2, 4],
vec![2, 3, 4],
vec![1],
vec![1, 3],
vec![1, 2],
vec![1, 2, 3],
vec![1, 4],
vec![1, 3, 4],
vec![1, 2, 4],
vec![1, 2, 3, 4],
];
subsets.iter_mut().for_each(|x| x.sort());
subsets.sort();
expect.iter_mut().for_each(|x| x.sort());
expect.sort();
assert_eq!(subsets, expect);

let mut subsets = Solution::subsets(vec![0]);
let mut expect = vec![vec![], vec![0]];
subsets.iter_mut().for_each(|x| x.sort());
subsets.sort();
expect.iter_mut().for_each(|x| x.sort());
expect.sort();
assert_eq!(subsets, expect);

assert_eq!(Solution::subsets(vec![]), vec![vec![]]);
}
}
Loading