-
Notifications
You must be signed in to change notification settings - Fork 0
solve: 78.Subsets #51
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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)になるとのことだった。 | ||
| なので、実行時間の概算がかなり近い数値で見積もれたのはたまたまかと思った。 | ||
|
|
||
| 所感 | ||
| - 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![]]); | ||
| } | ||
| } | ||
| 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)); | ||
|
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. ここ、extend で shallow copy (move)発生という理解でいいですか? make_subsets にもう一つ引数を増やし、与えられた最後の引数に結果を extend する関数とするとこの問題は解決しますね。
Owner
Author
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.
はい。Self::make_subsetsの結果をmoveしています。
副作用で引数に変更を加えていくと理解しました。 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![]]); | ||
| } | ||
| } | ||
| 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![]]); | ||
| } | ||
| } |
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.
nが10で log n と n では4倍くらいしか違わないはずです。
が、GPT-5.2 のその計算量評価は間違いでしょう。
f(n) = cost(sort(n)) + n * (cost(clone(n-1)) + f(n - 1)) となっているので、sort と clone 部分は最後定数になるはずです。
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.
ありがとうございます。
f(n) = cost(sort(n)) + n * (cost(clone(n-1)) + f(n - 1)) のとき、n * f(n - 1)の部分が支配的であるので時間計算量はO(n!)になると理解しました。
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.
そうですね。具体的に代入してみれば明らかに感じるでしょう。