From afc2db51e553a0d33cf63723f2663f9428042301 Mon Sep 17 00:00:00 2001 From: t9a Date: Sat, 7 Mar 2026 13:44:51 +0900 Subject: [PATCH 1/2] solve: 392.Is Subsequence --- src/bin/step1.rs | 105 ++++++++++++++++++++++++++++++++++++++ src/bin/step2.rs | 112 +++++++++++++++++++++++++++++++++++++++++ src/bin/step2a.rs | 68 +++++++++++++++++++++++++ src/bin/step2b.rs | 66 ++++++++++++++++++++++++ src/bin/step2c.rs | 126 ++++++++++++++++++++++++++++++++++++++++++++++ src/bin/step3.rs | 86 +++++++++++++++++++++++++++++++ 6 files changed, 563 insertions(+) create mode 100644 src/bin/step1.rs create mode 100644 src/bin/step2.rs create mode 100644 src/bin/step2a.rs create mode 100644 src/bin/step2b.rs create mode 100644 src/bin/step2c.rs create mode 100644 src/bin/step3.rs diff --git a/src/bin/step1.rs b/src/bin/step1.rs new file mode 100644 index 0000000..86be73f --- /dev/null +++ b/src/bin/step1.rs @@ -0,0 +1,105 @@ +// Step1 +// 目的: 方法を思いつく + +// 方法 +// 5分考えてわからなかったら答えをみる +// 答えを見て理解したと思ったら全部消して答えを隠して書く +// 5分筆が止まったらもう一回みて全部消す +// 正解したら終わり + +/* + 問題の理解 + - 文字列s,tが与えられるのでsがtの部分文字列であればtrueを返す。そうでなければfalseを返す。 + s="ace",t="abcde" out=true + s="aec",t="abcde" out=false + + 何を考えて解いていたか + - 文字sのi番目の文字を表すポインタと、文字tのj番目の文字を表すポインタをそれぞれ用意する。 + - tの方が文字列長が長いので、tをループで回す。 + - 問題の制約上はあり得ないが、s.len < t.lenを関数最初に確認すると良さそう。 + - s.len, t.len = 0のときはtrueになるのかが分からない。問題文からも読み取れない。部分列ではないように見えるのでfalseとして扱う。 + - s[i] == t[j]を見つけるたびにiをインクリメントする。 + - 関数最後で i == s.len を返す。 + このロジックで解けるなら計算量は以下になりそう。 + n = t.len + 時間計算量: O(n) + 空間計算量: O(1) + 入力の制約上は t.len < 10 ^ 4 + 10 ^ 4 / 10 ^ 8 = 0.0001 = 0.1msとなり問題ない + 実装しようとして気付いたが与えられる文字列の型がStringなのでVecにすると空間計算量: O(n)になった。 + s="",t=""のテストケースでWrong Answerとなった。ここはtrueだった。 + 入力が空文字のときにfalseとするエッジケースの条件分岐を消してAcceptedとなった。 + + 何がわからなかったか + - 空文字は空文字のsubsequenceであるということが分からなかった。 + + 正解してから気づいたこと + - 今回の問題では入力の制約からアルファベット小文字のみなので、s.as_bytes()[i]のようにアクセスすれば空間計算量はO(1)になると思った。 + - ただし、マルチバイト文字が渡されるとこの実装は破綻する。1文字の単位を正しく扱えないため。 + - is_subsequenceがString型であることを考えると、s.as_bytes()[i]による文字参照はやっかいなバグを埋め込むことになりそうなので忌避感がある。 + - t.len() < s.len() は文字列のバイト数を比較する。自分が考えていた用途では文字数を数えたかったので正しくは if t.chars().count() < s.chars().count() とするべき。 + - Leet CodeのFollow Upについて。 + - 入力として与えられるsが複数ある場合にコードをどう変えるかという内容。sの個数の上限は 10 ^ 9 + - そのまま外側にループを足すと、n = t.len, m = s_list.len として、O(n * m) + - 10 ^ 4 * 10 ^ 9 / 10 ^ 8 = 100,000秒(約27時間)となり、バッチ処理とかでなければ現実的ではない。 + - s_listの文字列s_list[i]の文字s[i]と対応するindexでHashMapを作る。 + - sのi文字目をkey,i文字目に出現する文字を HashMap> に詰めていくイメージ + - HashMapのkeyはkey数100(sの最大文字列長さ),valueはアルファベット小文字26種類が入る。 + ここまで考えて、最終的にsubsequenceかどうかをどのように判定すればよいか分からなくて手が止まったのでスキップ +*/ + +pub struct Solution {} +impl Solution { + pub fn is_subsequence(s: String, t: String) -> bool { + if t.len() < s.len() { + return false; + } + + let s_chars = s.chars().collect::>(); + let mut s_char_index = 0; + for t_char in t.chars() { + let Some(s_char) = s_chars.get(s_char_index) else { + break; + }; + + if *s_char == t_char { + s_char_index += 1; + } + } + + s_char_index == s_chars.iter().count() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step1_subsequence_test() { + assert_eq!( + Solution::is_subsequence("ace".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("a".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("".to_string(), "".to_string()), + true + ); + } + + #[test] + fn step1_not_subsequence_test() { + assert_eq!( + Solution::is_subsequence("aec".to_string(), "abcde".to_string()), + false + ); + assert_eq!( + Solution::is_subsequence("abcde".to_string(), "ace".to_string()), + false + ); + } +} diff --git a/src/bin/step2.rs b/src/bin/step2.rs new file mode 100644 index 0000000..f3f2095 --- /dev/null +++ b/src/bin/step2.rs @@ -0,0 +1,112 @@ +// Step2 +// 目的: 自然な書き方を考えて整理する + +// 方法 +// Step1のコードを読みやすくしてみる +// 他の人のコードを2つは読んでみること +// 正解したら終わり + +// 以下をメモに残すこと +// 講師陣はどのようなコメントを残すだろうか? +// 他の人のコードを読んで考えたこと +// 改善する時に考えたこと + +/* + 他の人のコードを読んで考えたこと + https://github.com/olsen-blue/Arai60/pull/58#discussion_r2033296195 + > while 1: と書くのはあまり見ないように思います。 whilte True: のほうをよく見ます。 + - 確かに while 1 は初めて見た。 + + https://github.com/olsen-blue/Arai60/pull/58/changes#diff-0fa674be955a983fbef3f0f1952784256410e7cd97ff5fa05b6d6b5cfa09d3c0R25 + - 再帰の実装は思いつかなかった。読む練習に良さそう。 + + https://github.com/hayashi-ay/leetcode/pull/64/changes#diff-0fa674be955a983fbef3f0f1952784256410e7cd97ff5fa05b6d6b5cfa09d3c0R57 + - 自分は2つのポインタ両方をループの中でインクリメントするよりは、片方をforで単調増加させて自動的に増やすみたいな感覚で頭の中から追い出すのが好きなんだなと思った。 + + https://github.com/Yoshiki-Iwasa/Arai60/pull/62/changes#diff-5009dbf0a62afffd2bd2b44a9a2ab6dc842111a7b13503ad1fd3b2ca4ec87e11R32 + - 数え上げるのではなく、見つけ文字をpop()で取り出して、最後に全部取り出せたかを確認している。思いつかなかった。VecDequeを使うとreverseせずにいけるかと思った。 + + https://github.com/Yoshiki-Iwasa/Arai60/pull/62/changes#diff-e791ee6de1405ea8b9a39688aba429a08271228a394e40fd01307838a566a994R87 + - Follow upの解法に見える。 + + https://github.com/Yoshiki-Iwasa/Arai60/pull/62/changes#diff-e791ee6de1405ea8b9a39688aba429a08271228a394e40fd01307838a566a994R131 + - DPの解法。ぱっと見何をしているかわからない。読むのに時間かかりそう。 + + https://github.com/naoto-iwase/leetcode/pull/58/changes#diff-5fd32322bb6a97c24c6f249cdb696a317abc0f8aa8ab79d23c39f17aad61b701R155 + - Follow upの解法。 + + - indicesはindexの複数形ということを知った。英語的にはindexesでも良いらしい。Rustではメソッド名にindicesが使われていた。(char:char_indices) + https://doc.rust-lang.org/std/primitive.str.html#method.char_indices + + + 改善する時に考えたこと + - 文字数を数えるのは s.len() ではなくて、s.chars().count() を使う。 + - VecDequeで見つけた文字をpop_front()していき、最終的にすべて取り出せたかでsubsequenceであるかを判定する + + 所感 + - よりシンプルになったと思う。 + - 再帰の実装を写経しておく。step2a.rs + https://github.com/olsen-blue/Arai60/pull/58/changes#diff-0fa674be955a983fbef3f0f1952784256410e7cd97ff5fa05b6d6b5cfa09d3c0R25 + - 2ポインタとwhileで回す解法は少し苦手意識があるので書いておく。step2b.rs + https://github.com/hayashi-ay/leetcode/pull/64/changes#diff-0fa674be955a983fbef3f0f1952784256410e7cd97ff5fa05b6d6b5cfa09d3c0R57 + - Follow upの解法を写経しておく。 + https://github.com/naoto-iwase/leetcode/pull/58/changes#diff-5fd32322bb6a97c24c6f249cdb696a317abc0f8aa8ab79d23c39f17aad61b701R49 + +*/ + +use std::collections::VecDeque; + +pub struct Solution {} +impl Solution { + pub fn is_subsequence(s: String, t: String) -> bool { + if t.chars().count() < s.chars().count() { + return false; + } + + let mut s_chars = VecDeque::from_iter(s.chars()); + for t_char in t.chars() { + let Some(s_char) = s_chars.front() else { + break; + }; + + if *s_char == t_char { + s_chars.pop_front(); + } + } + + s_chars.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2_subsequence_test() { + assert_eq!( + Solution::is_subsequence("ace".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("a".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("".to_string(), "".to_string()), + true + ); + } + + #[test] + fn step2_not_subsequence_test() { + assert_eq!( + Solution::is_subsequence("aec".to_string(), "abcde".to_string()), + false + ); + assert_eq!( + Solution::is_subsequence("abcde".to_string(), "ace".to_string()), + false + ); + } +} diff --git a/src/bin/step2a.rs b/src/bin/step2a.rs new file mode 100644 index 0000000..43a4a0c --- /dev/null +++ b/src/bin/step2a.rs @@ -0,0 +1,68 @@ +// Step2a +// 目的: 別の解法の練習(再帰) + +/* + https://github.com/olsen-blue/Arai60/pull/58/changes#diff-0fa674be955a983fbef3f0f1952784256410e7cd97ff5fa05b6d6b5cfa09d3c0R25 + 再帰の実装を写経しておく。 + + 所感 + - シンプルで分かりやすく選択肢として持っておきたい解法だと思った。 +*/ + +pub struct Solution {} +impl Solution { + pub fn is_subsequence(s: String, t: String) -> bool { + let s_chars = s.chars().collect::>(); + let t_chars = t.chars().collect::>(); + + Self::traverse_s_t(&s_chars, &t_chars, 0, 0) + } + + fn traverse_s_t(s_chars: &[char], t_chars: &[char], s_index: usize, t_index: usize) -> bool { + if s_index == s_chars.len() { + return true; + } + if t_index == t_chars.len() { + return false; + } + + if s_chars[s_index] == t_chars[t_index] { + return Self::traverse_s_t(s_chars, t_chars, s_index + 1, t_index + 1); + } + + Self::traverse_s_t(s_chars, t_chars, s_index, t_index + 1) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2a_subsequence_test() { + assert_eq!( + Solution::is_subsequence("ace".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("a".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("".to_string(), "".to_string()), + true + ); + } + + #[test] + fn step2a_not_subsequence_test() { + assert_eq!( + Solution::is_subsequence("aec".to_string(), "abcde".to_string()), + false + ); + assert_eq!( + Solution::is_subsequence("abcde".to_string(), "ace".to_string()), + false + ); + } +} diff --git a/src/bin/step2b.rs b/src/bin/step2b.rs new file mode 100644 index 0000000..dd861eb --- /dev/null +++ b/src/bin/step2b.rs @@ -0,0 +1,66 @@ +// Step2b +// 目的: 別の解法の練習(2ポインタ+while) + +/* + https://github.com/hayashi-ay/leetcode/pull/64/changes#diff-0fa674be955a983fbef3f0f1952784256410e7cd97ff5fa05b6d6b5cfa09d3c0R57 + 2ポインタの実装を写経しておく。 + + 所感 + - 解法を理解した状態だったのでスムーズに実装できた。 +*/ + +pub struct Solution {} +impl Solution { + pub fn is_subsequence(s: String, t: String) -> bool { + if t.chars().count() < s.chars().count() { + return false; + } + + let s_chars = s.chars().collect::>(); + let t_chars = t.chars().collect::>(); + let mut s_char_index = 0; + let mut t_char_index = 0; + + while s_char_index < s_chars.len() && t_char_index < t_chars.len() { + if s_chars[s_char_index] == t_chars[t_char_index] { + s_char_index += 1; + } + t_char_index += 1; + } + + s_char_index == s_chars.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2b_subsequence_test() { + assert_eq!( + Solution::is_subsequence("ace".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("a".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("".to_string(), "".to_string()), + true + ); + } + + #[test] + fn step2b_not_subsequence_test() { + assert_eq!( + Solution::is_subsequence("aec".to_string(), "abcde".to_string()), + false + ); + assert_eq!( + Solution::is_subsequence("abcde".to_string(), "ace".to_string()), + false + ); + } +} diff --git a/src/bin/step2c.rs b/src/bin/step2c.rs new file mode 100644 index 0000000..a27155f --- /dev/null +++ b/src/bin/step2c.rs @@ -0,0 +1,126 @@ +// Step2c +// 目的: Follow upの解法を写経しておく + +/* + Follow upの解法を写経しておく。 + https://github.com/naoto-iwase/leetcode/pull/58/changes#diff-5fd32322bb6a97c24c6f249cdb696a317abc0f8aa8ab79d23c39f17aad61b701R155 + + 解法の理解 + - tが持つある文字にO(1)でアクセスできるようにHashMapを作る + - 文字をkeyとして、indexを配列としてもつ + - s_list[i]の文字列sを走査しながら、tのHashMapに文字が含まれているかを見ていく。 + - 文字を見つけたら、tの中での位置(index)が前回よりも大きいかを確認する。 + - 大きければ前回位置を更新 + - 大きくなければ必要な文字を見つけられなかったので早期リターンでfalseを返す + - 最後まで到達すればtrue + + 所感 + - 二分探索を使うのは思いつかなかった。 + - 配列の中からこれまで見た値(index)よりも大きい値の位置を知りたいという問題設定ができれば、線形探索O(n)ではなく二分探索O(log n)を利用する方向が思いつくかなと思った。 +*/ + +use std::collections::HashMap; + +pub struct Solution {} +impl Solution { + /* + LeetCodeのFollow upの解法です。採点システムではジャッジできません。 + https://leetcode.com/problems/is-subsequence/description/ + > Follow up: Suppose there are lots of incoming s, say s1, s2, ..., sk where k >= 109, and you want to check one by one to see if t has its subsequence. In this scenario, how would you change your code? + */ + pub fn is_subsequence(s_list: Vec, t: String) -> bool { + let mut t_char_to_indecies: HashMap<_, Vec<_>> = HashMap::new(); + for (i, c) in t.chars().enumerate() { + t_char_to_indecies.entry(c).or_default().push(i); + } + + for s in s_list { + if Self::is_subsequence_helper(&s, &t_char_to_indecies) { + return true; + } + } + + false + } + + fn is_subsequence_helper(s: &str, t_char_to_indecies: &HashMap>) -> bool { + let mut previous_t_char_index = 0usize; + for s_char in s.chars() { + let Some(t_char_indecies) = t_char_to_indecies.get(&s_char) else { + return false; + }; + + // predicate: *i < previous_t_char_indexの評価結果がtrueとfalseで切り替わる境界が返される + let t_char_index = t_char_indecies.partition_point(|i| *i < previous_t_char_index); + if t_char_index == t_char_indecies.len() { + return false; + } + previous_t_char_index = t_char_indecies[t_char_index] + 1; + } + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2c_subsequence_test() { + assert_eq!( + Solution::is_subsequence( + vec!["abcf", "bcf", "abe"] + .into_iter() + .map(str::to_string) + .collect(), + "abcde".to_string() + ), + true + ); + assert_eq!( + Solution::is_subsequence( + vec!["ag", "bcf", "abe", "aa"] + .into_iter() + .map(str::to_string) + .collect(), + "abcdea".to_string() + ), + true + ); + assert_eq!( + Solution::is_subsequence( + vec!["あいうえおか", "きく", "さしすせそ", "うえ"] + .into_iter() + .map(str::to_string) + .collect(), + "あいうえお".to_string() + ), + true + ); + } + + #[test] + fn step2c_not_subsequence_test() { + assert_eq!( + Solution::is_subsequence( + vec!["abcf", "bcf", "def"] + .into_iter() + .map(str::to_string) + .collect(), + "abcde".to_string() + ), + false + ); + assert_eq!( + Solution::is_subsequence( + vec!["あいうえおか", "きく", "さしすせそ", "うえ"] + .into_iter() + .map(str::to_string) + .collect(), + "てと".to_string() + ), + false + ); + } +} diff --git a/src/bin/step3.rs b/src/bin/step3.rs new file mode 100644 index 0000000..4c9f216 --- /dev/null +++ b/src/bin/step3.rs @@ -0,0 +1,86 @@ +// Step3 +// 目的: 覚えられないのは、なんか素直じゃないはずなので、そこを探し、ゴールに到達する + +// 方法 +// 時間を測りながらもう一度解く +// 10分以内に一度もエラーを吐かず正解 +// これを3回連続でできたら終わり +// レビューを受ける +// 作れないデータ構造があった場合は別途自作すること + +/* + n = t.len + m = s.len + 時間計算量: O(n) + 空間計算量: O(m) +*/ + +/* + 1回目: 2分20秒 + 2回目: 1分58秒 + 3回目: 1分42秒 +*/ + +/* + 所感 + - 文字列を文字に分割して扱う点でgrapheme clusterを思い出した。 + 問題の制約では英字小文字しか入ってこないが、文字列という広い制約だった場合、見た目の一文字(grapheme cluster)と複数のコードポイントで表されるUnicode文字などに思いを馳せながら実装しないとやっかいなバグになりそう。 + 自分で実装するのではなく、適切な場面で外部ライブラリを使うなどの判断ができればよいと思った。 +*/ + +use std::collections::VecDeque; + +pub struct Solution {} +impl Solution { + pub fn is_subsequence(s: String, t: String) -> bool { + if t.chars().count() < s.chars().count() { + return false; + } + + let mut s_chars = VecDeque::from_iter(s.chars()); + for t_char in t.chars() { + let Some(s_char) = s_chars.front() else { + break; + }; + + if *s_char == t_char { + s_chars.pop_front(); + } + } + + s_chars.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step3_subsequence_test() { + assert_eq!( + Solution::is_subsequence("ace".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("a".to_string(), "abcde".to_string()), + true + ); + assert_eq!( + Solution::is_subsequence("".to_string(), "".to_string()), + true + ); + } + + #[test] + fn step3_not_subsequence_test() { + assert_eq!( + Solution::is_subsequence("aec".to_string(), "abcde".to_string()), + false + ); + assert_eq!( + Solution::is_subsequence("abcde".to_string(), "ace".to_string()), + false + ); + } +} From 6e5421cb8bf0d3c9e739c28b98ea3f88fb857e1a Mon Sep 17 00:00:00 2001 From: t9a Date: Sat, 7 Mar 2026 13:50:40 +0900 Subject: [PATCH 2/2] =?UTF-8?q?any()=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=8C=E4=BD=BF=E3=81=88=E3=82=8B=E3=81=93=E3=81=A8=E3=81=AB?= =?UTF-8?q?=E6=B0=97=E4=BB=98=E3=81=84=E3=81=9F=E3=81=AE=E3=81=A7=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bin/step2c.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/bin/step2c.rs b/src/bin/step2c.rs index a27155f..b53175a 100644 --- a/src/bin/step2c.rs +++ b/src/bin/step2c.rs @@ -34,13 +34,9 @@ impl Solution { t_char_to_indecies.entry(c).or_default().push(i); } - for s in s_list { - if Self::is_subsequence_helper(&s, &t_char_to_indecies) { - return true; - } - } - - false + s_list + .iter() + .any(|s| Self::is_subsequence_helper(s, &t_char_to_indecies)) } fn is_subsequence_helper(s: &str, t_char_to_indecies: &HashMap>) -> bool {