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
105 changes: 105 additions & 0 deletions src/bin/step1.rs
Original file line number Diff line number Diff line change
@@ -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<char>にすると空間計算量: 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<usize,HashSet<char>> に詰めていくイメージ
- 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::<Vec<_>>();
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
);
}
}
112 changes: 112 additions & 0 deletions src/bin/step2.rs
Original file line number Diff line number Diff line change
@@ -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
);
}
}
68 changes: 68 additions & 0 deletions src/bin/step2a.rs
Original file line number Diff line number Diff line change
@@ -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::<Vec<_>>();
let t_chars = t.chars().collect::<Vec<_>>();

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
);
}
}
66 changes: 66 additions & 0 deletions src/bin/step2b.rs
Original file line number Diff line number Diff line change
@@ -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::<Vec<_>>();
let t_chars = t.chars().collect::<Vec<_>>();
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
);
}
}
Loading