-
Notifications
You must be signed in to change notification settings - Fork 0
solve: 141.Linked List Cycle #7
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 | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,26 +12,115 @@ | |||||||||||||||||
| // 改善する時に考えたこと | ||||||||||||||||||
|
|
||||||||||||||||||
| /* | ||||||||||||||||||
| 講師陣はどのようなコメントを残すだろうか? | ||||||||||||||||||
| - | ||||||||||||||||||
|
|
||||||||||||||||||
| 他の人のコードを読んで考えたこと | ||||||||||||||||||
| - | ||||||||||||||||||
|
|
||||||||||||||||||
| 他の想定ユースケース | ||||||||||||||||||
| - | ||||||||||||||||||
| - 「フロイドの循環検出法」というアルゴリズムでこの問題を解けそうだということが分かった。 | ||||||||||||||||||
| フロイドの循環検出法では2人の内1人が一歩先にいる状態から走査をスタートする。 | ||||||||||||||||||
| サイクルごとに2人の現在位置が同じでないかを確認していき同じであれば循環しているので走査を終了。 | ||||||||||||||||||
| サイクルごとにスタート位置が手前の方は一歩ずつ進む。一歩先にいる方は2歩進んでいく。 | ||||||||||||||||||
| 一歩先の人が次のノードを見つけられなければ循環していないので終了。 | ||||||||||||||||||
| この方法では走査しながら確認できる(ワンパス)ので追加の補助空間計算量がかからず、空間計算量がO(1)となる。 | ||||||||||||||||||
| step2_1_floyds_cycle_detection.rsで一応実装してみる。 | ||||||||||||||||||
| https://leetcode.com/problems/linked-list-cycle/solutions/4831939/brute-optimal-both-approach-full-explained-java-c-python3-rust-go-javascript/ | ||||||||||||||||||
| - いくつかコードを読んだが上記のアルゴリズムか、自分の採用したHashSetを用いた解法くらいしか見当たらなかった。 | ||||||||||||||||||
| - 問題を解くこと自体よりもLeetCodeがRustに対応していなかったので単方向リンクリスト自体を実装する必要が出てきたのが良い経験になった。 | ||||||||||||||||||
| 単方向リンクリストの循環検出方法自体はすぐに思いついた。単方向リンクリスト自体の実装に苦戦したがRustの理解が深まった。 | ||||||||||||||||||
|
|
||||||||||||||||||
| 改善する時に考えたこと | ||||||||||||||||||
| - | ||||||||||||||||||
| - 変数名について単にnodeとしていた箇所をcurrent_nodeにすることで、曖昧さを軽減した。 | ||||||||||||||||||
| - よく理解せずに混乱しながら利用していたRwLock<T>,Box<T>の利用をやめた。 | ||||||||||||||||||
| - Rc<T>のデータに対しては.clone()ではなくRc::clone()で明示的にリファレンスカウンタをインクリメントするようにした。 | ||||||||||||||||||
| 内部的には同じだが、公式ドキュメントで明示的に使い分けることが推奨されていたため。 | ||||||||||||||||||
| https://doc.rust-jp.rs/book-ja/ch15-04-rc.html#:~:text=%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82-,Rc,%E3%81%99 | ||||||||||||||||||
|
|
||||||||||||||||||
| - テストコードで利用しているbuild_list_with_cycleについて、インデックスアクセスの方法を添字からgetによる安全な(エラーハンドリング可能な)方法にする。 | ||||||||||||||||||
| */ | ||||||||||||||||||
|
|
||||||||||||||||||
| use std::{cell::RefCell, collections::HashSet, rc::Rc}; | ||||||||||||||||||
|
|
||||||||||||||||||
| pub struct ListNode { | ||||||||||||||||||
| pub next: Option<Rc<RefCell<ListNode>>>, | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+40
to
+42
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. Option<Rc<RefCell<ListNode>>>がボイラープレートっぽいので、型エイリアスを使うのはどうでしょうか。
Suggested change
テストとか書くときも少し気が楽になるかもです。
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. ありがとうございます。型エイリアスは活用したことがなく提案いただくまで選択肢にもなかったので、すごく勉強になりました。(アハ体験でした。) |
||||||||||||||||||
|
|
||||||||||||||||||
| pub struct Solution {} | ||||||||||||||||||
| impl Solution {} | ||||||||||||||||||
| impl Solution { | ||||||||||||||||||
| pub fn has_cycle(head: Option<Rc<RefCell<ListNode>>>) -> bool { | ||||||||||||||||||
| let mut visited_node: HashSet<_> = HashSet::new(); | ||||||||||||||||||
| let mut next_node = head; | ||||||||||||||||||
|
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. レビューが遅くなり、申し訳ありません。 |
||||||||||||||||||
|
|
||||||||||||||||||
| while let Some(current_node) = next_node { | ||||||||||||||||||
| if !visited_node.insert(current_node.as_ptr()) { | ||||||||||||||||||
| return true; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| next_node = current_node.borrow().next.as_ref().map(Rc::clone); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| false | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[cfg(test)] | ||||||||||||||||||
| mod tests { | ||||||||||||||||||
| use super::*; | ||||||||||||||||||
|
|
||||||||||||||||||
| fn build_list_with_cycle( | ||||||||||||||||||
| cycle_position: Option<usize>, | ||||||||||||||||||
| list_len: usize, | ||||||||||||||||||
| ) -> Option<Rc<RefCell<ListNode>>> { | ||||||||||||||||||
| if list_len == 0 { | ||||||||||||||||||
| panic!("invalid list_len require 1"); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| let nodes = (0..list_len) | ||||||||||||||||||
| .map(|_| Rc::new(RefCell::new(ListNode { next: None }))) | ||||||||||||||||||
| .collect::<Vec<_>>(); | ||||||||||||||||||
| let tail_position = list_len - 1; | ||||||||||||||||||
|
|
||||||||||||||||||
| // nodeのnextに接続する | ||||||||||||||||||
| for (i, node) in nodes.iter().enumerate() { | ||||||||||||||||||
| if let Some(next_node) = nodes.get(i + 1) { | ||||||||||||||||||
| node.borrow_mut().next = Some(Rc::clone(next_node)); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // tail_nodeのnextにcycle_positionで指定されたnodeを接続 | ||||||||||||||||||
| if let Some(cycle_position) = cycle_position { | ||||||||||||||||||
| if let (Some(tail_node), Some(cycle_to_node)) = | ||||||||||||||||||
| (nodes.get(tail_position), nodes.get(cycle_position)) | ||||||||||||||||||
| { | ||||||||||||||||||
| tail_node.borrow_mut().next = Some(Rc::clone(&cycle_to_node)); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| Some(Rc::clone(&nodes[0])) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn step2_test() {} | ||||||||||||||||||
| fn step2_no_cycle_test() { | ||||||||||||||||||
| let expect = false; | ||||||||||||||||||
| let no_cycle = build_list_with_cycle(None, 4); | ||||||||||||||||||
| assert_eq!(Solution::has_cycle(no_cycle), expect); | ||||||||||||||||||
|
|
||||||||||||||||||
| let no_cycle = build_list_with_cycle(None, 1); | ||||||||||||||||||
| assert_eq!(Solution::has_cycle(no_cycle), expect); | ||||||||||||||||||
|
|
||||||||||||||||||
| let no_cycle = build_list_with_cycle(Some(4), 2); | ||||||||||||||||||
| assert_eq!(Solution::has_cycle(no_cycle), expect); | ||||||||||||||||||
|
|
||||||||||||||||||
| let no_cycle = build_list_with_cycle(Some(2), 2); | ||||||||||||||||||
| assert_eq!(Solution::has_cycle(no_cycle), expect); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn step1_with_cycle_test() { | ||||||||||||||||||
| let expect = true; | ||||||||||||||||||
| let with_cycle = build_list_with_cycle(Some(1), 4); | ||||||||||||||||||
| assert_eq!(Solution::has_cycle(with_cycle), expect); | ||||||||||||||||||
|
|
||||||||||||||||||
| let with_cycle = build_list_with_cycle(Some(1), 3); | ||||||||||||||||||
| assert_eq!(Solution::has_cycle(with_cycle), expect); | ||||||||||||||||||
|
|
||||||||||||||||||
| let with_cycle = build_list_with_cycle(Some(2), 3); | ||||||||||||||||||
| assert_eq!(Solution::has_cycle(with_cycle), expect); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| // Step2_1 | ||
| // 目的: floyd's cycle detectionの実装をしてみる。 | ||
|
|
||
| /* | ||
| フロイドの循環検出法では2人の内1人が一歩先にいる状態から走査をスタートする。 | ||
| サイクルごとに2人の現在位置が同じでないかを確認していき同じであれば循環しているので走査を終了。 | ||
| サイクルごとにスタート位置が手前の方は一歩ずつ進む。一歩先にいる方は二歩進んでいく。 | ||
| 一歩先の人が次のノードを見つけられなければ循環していないので終了。 | ||
| この方法では走査しながら確認できる(ワンパス)ので追加の補助空間計算量がかからず、空間計算量がO(1)となる。 | ||
| https://leetcode.com/problems/linked-list-cycle/solutions/4831939/brute-optimal-both-approach-full-explained-java-c-python3-rust-go-javascript/ | ||
| */ | ||
|
|
||
| /* | ||
| 入力のリンクリストのサイズをNとする | ||
| 時間計算量: O(N) 全走査するため | ||
| 空間計算量: O(1) 全走査しながらインプレイスで重複チェックしており、HashSetなどに記録していないため | ||
| */ | ||
|
|
||
| use std::{cell::RefCell, rc::Rc}; | ||
|
|
||
| pub struct ListNode { | ||
| pub next: Option<Rc<RefCell<ListNode>>>, | ||
| } | ||
|
|
||
| pub struct Solution {} | ||
| impl Solution { | ||
| pub fn has_cycle(head: Option<Rc<RefCell<ListNode>>>) -> bool { | ||
| let (mut slow_node, mut fast_node) = { | ||
| let current = head.as_ref().map(Rc::clone); | ||
| let next = head.and_then(|head_node| head_node.borrow().next.as_ref().map(Rc::clone)); | ||
|
|
||
| (current, next) | ||
| }; | ||
|
|
||
| while let (Some(slow), Some(fast)) = (slow_node, fast_node) { | ||
| if Rc::ptr_eq(&slow, &fast) { | ||
| return true; | ||
| } | ||
|
|
||
| slow_node = slow.borrow().next.as_ref().map(Rc::clone); | ||
|
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. うーん、こういうヘルパーを用意すると少しきれいになりますかね。 fn next_node(node: Option<Rc<RefCell<ListNode>>>) -> Option<Rc<RefCell<ListNode>>> {
node.and_then(|n| n.borrow().next.clone())
}
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. ありがとうございます。メソッドチェーンが長いなとは書いていて思ったのですが、言語仕様的に必要な呼び出しだから仕方ないかと違和感をスルーしていた部分でした。 pub fn has_cycle(head: Option<Rc<RefCell<ListNode>>>) -> bool {
let (mut slow_node, mut fast_node) = {
let current = head.as_ref().map(Rc::clone);
let next = Self::next_node(head);
(current, next)
};
while let (Some(slow), Some(fast)) = (slow_node, fast_node) {
if Rc::ptr_eq(&slow, &fast) {
return true;
}
slow_node = Self::next_node(Some(slow));
fast_node = Self::next_node(Self::next_node(Some(fast)));
}
false
}
fn next_node(node: Option<Rc<RefCell<ListNode>>>) -> Option<Rc<RefCell<ListNode>>> {
node.and_then(|n| n.borrow().next.as_ref().map(Rc::clone))
} |
||
| fast_node = fast | ||
| .as_ref() | ||
| .borrow() | ||
| .next | ||
| .as_ref() | ||
| .and_then(|next_node| next_node.as_ref().borrow().next.as_ref().map(Rc::clone)); | ||
| } | ||
|
|
||
| false | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| fn build_list_with_cycle( | ||
| cycle_position: Option<usize>, | ||
| list_len: usize, | ||
| ) -> Option<Rc<RefCell<ListNode>>> { | ||
| if list_len == 0 { | ||
| panic!("invalid list_len require 1"); | ||
| } | ||
|
|
||
| let nodes = (0..list_len) | ||
| .map(|_| Rc::new(RefCell::new(ListNode { next: None }))) | ||
| .collect::<Vec<_>>(); | ||
| let tail_position = list_len - 1; | ||
|
|
||
| // nodeのnextに接続する | ||
| for (i, node) in nodes.iter().enumerate() { | ||
| if let Some(next_node) = nodes.get(i + 1) { | ||
| node.borrow_mut().next = Some(Rc::clone(&next_node)); | ||
| } | ||
| } | ||
|
|
||
| // tail_nodeのnextにcycle_positionで指定されたnodeを接続 | ||
| if let Some(cycle_position) = cycle_position { | ||
| if let (Some(tail_node), Some(cycle_to_node)) = | ||
| (nodes.get(tail_position), nodes.get(cycle_position)) | ||
| { | ||
| tail_node.borrow_mut().next = Some(Rc::clone(&cycle_to_node)); | ||
| } | ||
| } | ||
|
|
||
| Some(Rc::clone(&nodes[0])) | ||
| } | ||
|
|
||
| #[test] | ||
| fn step2_1_no_cycle_test() { | ||
| let expect = false; | ||
| let no_cycle = build_list_with_cycle(None, 4); | ||
| assert_eq!(Solution::has_cycle(no_cycle), expect); | ||
|
|
||
| let no_cycle = build_list_with_cycle(None, 1); | ||
| assert_eq!(Solution::has_cycle(no_cycle), expect); | ||
|
|
||
| let no_cycle = build_list_with_cycle(Some(4), 2); | ||
| assert_eq!(Solution::has_cycle(no_cycle), expect); | ||
|
|
||
| let no_cycle = build_list_with_cycle(Some(2), 2); | ||
| assert_eq!(Solution::has_cycle(no_cycle), expect); | ||
| } | ||
|
|
||
| #[test] | ||
| fn step2_1_with_cycle_test() { | ||
| let expect = true; | ||
| let with_cycle = build_list_with_cycle(Some(1), 4); | ||
| assert_eq!(Solution::has_cycle(with_cycle), expect); | ||
|
|
||
| let with_cycle = build_list_with_cycle(Some(1), 3); | ||
| assert_eq!(Solution::has_cycle(with_cycle), expect); | ||
|
|
||
| let with_cycle = build_list_with_cycle(Some(2), 3); | ||
| assert_eq!(Solution::has_cycle(with_cycle), expect); | ||
| } | ||
| } | ||
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.
変数名ですがnodeのままでもいいかなと思います。
current_nodeだとcurrentと対になるようなノード(previous, nextなど)があるかもしれないという連想が働くので、ちょっとだけ読み手のコストが増す気がします。
Zun-U/coding-practice-mochi0123#2 (comment)