-
Notifications
You must be signed in to change notification settings - Fork 0
solve: 33.Search in Rotated Sorted Array #43
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
Open
t9a-dev
wants to merge
1
commit into
main
Choose a base branch
from
33.Search-in-Rotated-Sorted-Array
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| // Step1 | ||
| // 目的: 方法を思いつく | ||
|
|
||
| // 方法 | ||
| // 5分考えてわからなかったら答えをみる | ||
| // 答えを見て理解したと思ったら全部消して答えを隠して書く | ||
| // 5分筆が止まったらもう一回みて全部消す | ||
| // 正解したら終わり | ||
|
|
||
| /* | ||
| 問題の理解 | ||
| - 整数からなる配列numsと整数targetが与えられる。numsにtargetが含まれる場合はその位置を返す。見つからなければ-1を返す。 | ||
| numsはソートされた配列をk回転させたものが与えられる。[0,1,2,4,5,6,7] を3回転すると[4,5,6,7,0,1,2]となる。kの位置で配列を分割して左右を入れ替えるイメージ。 | ||
| numsに含まれる値は一意である。 | ||
| 制約として時間計算量O(log n)を満たす必要がある。 | ||
|
|
||
| 何を考えて解いていたか | ||
| - lower_boundを探して、探索終了後にtargetと等しいか一度確認して答えを返す方針で解く。 | ||
| この実装が難しそうなら、探索中にtarget == nums[i] を見つけたら早期リターンする。 | ||
| [start,end)半開区間で扱う。 | ||
| この考え方だと、middleの左右にmiddleよりも大きい値が存在するケースを処理できない。不変条件が壊れる。 | ||
| [start,end]な閉区間にしてtargetがありそうな範囲に絞り込んでいって、探索終了時にnums[end] == targetとなるかを確認するほうが良さそう | ||
| ここまで考えて手が止まってので答えを見る。 | ||
|
|
||
| 何がわからなかったか | ||
| - 回転後の配列でmiddleの左右どちら側にtargetがあるのかどう判断すればよいか分からなかった。 | ||
|
|
||
| 解答の理解 | ||
| https://leetcode.com/problems/search-in-rotated-sorted-array/solutions/3879263/100-binary-search-easy-video-ologn-optim-yp2k/ | ||
| - start ~ middle , middle ~ end どちらがソートされているかを判定する。 | ||
| - 元々ソートされていた配列をk回転させた後の配列は中央位置から見て左右どちらかの区間は必ずソートされている性質を持つ。 | ||
| - ソートされている方の範囲で値を探す。 | ||
| - targetがソートされている範囲に含まれているかを判定する。 | ||
| - ソートされている範囲にtargetが含まれていれば、探索範囲をソートされている範囲に縮小する。 | ||
| - ソートされている範囲にtargetが含まれていなければ、ソートされている範囲を探索範囲外にする。 | ||
|
|
||
| 正解してから気づいたこと | ||
| - この解法で解くにしても、if-elseのネストはもう少しなんとかしたい。 | ||
| - continueにしても良いが、場合分けなのでelseの方がより意図に対応している感じがする。 | ||
| - 早期リターンしない解法はstep2で他の人のコードを見つつ練習する。 | ||
| */ | ||
|
|
||
| pub struct Solution {} | ||
| impl Solution { | ||
| pub fn search(nums: Vec<i32>, target: i32) -> i32 { | ||
| if nums.is_empty() { | ||
| return -1; | ||
| } | ||
|
|
||
| let mut start = 0; | ||
| let mut end = nums.len() - 1; | ||
|
|
||
| while start <= end { | ||
| let middle = start + (end - start) / 2; | ||
|
|
||
| if nums[middle] == target { | ||
| return middle as i32; | ||
| } | ||
|
|
||
| let is_start_side_sorted = nums[start] <= nums[middle]; | ||
| let is_target_in_start_side = nums[start] <= target && target <= nums[middle]; | ||
| let is_target_in_end_side = nums[middle] < target && target <= nums[end]; | ||
|
|
||
| if is_start_side_sorted { | ||
| if is_target_in_start_side { | ||
| end = middle - 1; | ||
| } else { | ||
| start = middle + 1; | ||
| } | ||
| } else { | ||
| if is_target_in_end_side { | ||
| start = middle + 1; | ||
| } else { | ||
| end = middle - 1; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| -1 | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn step1_test() { | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 0), 4); | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 3), -1); | ||
| assert_eq!(Solution::search(vec![1], 0), -1); | ||
| assert_eq!(Solution::search(vec![], 0), -1); | ||
|
|
||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 2), 6); | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 4), 0); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 1), 1); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 5), 0); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 3), 2); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| // Step2 | ||
| // 目的: 自然な書き方を考えて整理する | ||
|
|
||
| // 方法 | ||
| // Step1のコードを読みやすくしてみる | ||
| // 他の人のコードを2つは読んでみること | ||
| // 正解したら終わり | ||
|
|
||
| // 以下をメモに残すこと | ||
| // 講師陣はどのようなコメントを残すだろうか? | ||
| // 他の人のコードを読んで考えたこと | ||
| // 改善する時に考えたこと | ||
|
|
||
| /* | ||
| 他の人のコードを読んで考えたこと | ||
| https://github.com/h1rosaka/arai60/pull/45#discussion_r2529537136 | ||
| - 探索中に早期リターンせずに、探索終了時のポインタ位置の値が等しいかどうかで結果を返す方式。練習しておきたい解法。 | ||
|
|
||
| https://github.com/t9a-dev/LeetCode_arai60/pull/42/changes/BASE..ba5b009c3fd1c49d89c5d2e1c2c09e7302f3b7d4#r2650867711 | ||
| - 自分がもらったレビューコメント。 | ||
| 回転済みの配列において、nums.last()は右区間に含まれる値になる。 | ||
| nums[i] <= nums.last()と比較することで、nums[i]が右区間に属するかを判定している。 | ||
| 完璧に理解できているとは言えない(自分の道具として思い通りに扱えない)ものの、何を言っているのかは分かる感じ。 | ||
|
|
||
| https://github.com/Yoshiki-Iwasa/Arai60/pull/36#discussion_r1712955053 | ||
| ペア?にするという別の解法。 | ||
| ぱっと見よく分からないが二分探索の理解が危ういという自覚があるのでなるべく別の解法も試したい。step2a.rsでやってみる。 | ||
|
|
||
| 改善する時に考えたこと | ||
| - 早期リターンせずに探索終了時のポインタから答えを求める解法を実装して理解を深める。 | ||
| - 回転範囲の判定をフラグ変数にすると文脈が分断される気がしたので今回は止めた。一度しか利用しないのでその場で条件を読んだ方が良いという感覚。 | ||
|
|
||
| 所感 | ||
| - step1の解法と比べて、回転している区間の処理とソートされている区間の単純な二分探索処理に分けられている。 | ||
| 問題を細分化して、個々に対応できているので良い解法だと思った。 | ||
| - 回転の処理でendの範囲外アクセスが起きないようになっている。 | ||
| 必要ないが、戻り値のチェックで配列アクセスするときに、end < nums.len() としておくことで自分を含めた読み手の不安を取り除きたいと思った。 | ||
| ここに不安を感じること自体が二分探索を理解しきれていないという感じもする。(コードを見て自信を持って範囲外アクセスは起きないと言えないところ) | ||
| https://github.com/hayashi-ay/leetcode/pull/49/changes#r1527147960 | ||
| 同じようなことを考えている人がいた。人が読むことを考えると絶対に削るべきというほどでも無いという感じだと思った。 | ||
| */ | ||
|
|
||
| pub struct Solution {} | ||
| impl Solution { | ||
| pub fn search(nums: Vec<i32>, target: i32) -> i32 { | ||
| if nums.is_empty() { | ||
| return -1; | ||
| } | ||
|
|
||
| let mut start = 0; | ||
| let mut end = nums.len(); | ||
|
|
||
| while start < end { | ||
| let middle = start + (end - start) / 2; | ||
| let last_num = *nums.last().unwrap(); | ||
|
|
||
| // middle ~ last_num はソートされているが、targetがlast_numを超えるので、この範囲にtargetは無い。 | ||
| // end側を捨てる。 | ||
| if nums[middle] <= last_num && target > last_num { | ||
| end = middle; | ||
| continue; | ||
| } | ||
|
|
||
| // middle が last_numを超えているので回転している。targetはlast_num以下なので、end側にtargetがある。 | ||
| // start側を捨てる。 | ||
| if nums[middle] > last_num && target <= last_num { | ||
| start = middle + 1; | ||
| continue; | ||
| } | ||
|
|
||
| // ここまで到達すると、探索範囲が回転していない(ソートされている)のでそのまま二分探索を行う。 | ||
| // endがlower_boundになる。 | ||
| if target <= nums[middle] { | ||
| end = middle; | ||
| } else { | ||
| start = middle + 1; | ||
| } | ||
| } | ||
|
|
||
| if end < nums.len() && nums[end] == target { | ||
| return end as i32; | ||
| } | ||
|
|
||
| -1 | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn step2_test() { | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 0), 4); | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 3), -1); | ||
| assert_eq!(Solution::search(vec![1], 0), -1); | ||
| assert_eq!(Solution::search(vec![], 0), -1); | ||
|
|
||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 2), 6); | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 4), 0); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 1), 1); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 5), 0); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 3), 2); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| // Step2a | ||
| // 目的: 別の解法を練習する | ||
|
|
||
| /* | ||
| https://github.com/Yoshiki-Iwasa/Arai60/pull/36#discussion_r1712955053 | ||
| ペア?にするという別の解法を実装してみる。 | ||
|
|
||
| 解法の理解 | ||
| - 結局よくわからなかったのでGPT-5.2に聞いて写経した。 | ||
| - binary_search_by_key(b, f)を引数に取る。 | ||
| https://doc.rust-lang.org/std/primitive.slice.html#method.binary_search_by_key | ||
| - bは検索キー。検索したいものそのもの。 | ||
| - fはキー抽出関数。bと比較できるように、比較に利用するキーを生成する。 | ||
| - (bool,i32)のタプルをb(検索キー)としている。 | ||
| - boolでどちらの区間かが分かるので、探索範囲を半分にできる。 | ||
| - target=0,last=2 | ||
| - [4, 5, 6, 7, 0, 1, 2] | ||
| - [F, F, F, F, T, T, T] (nums[i] <= last) | ||
| F=false,T=true | ||
| - target <= last は T となるので、右区間に含まれることが分かる。一度に半分に探索範囲を絞れる。 | ||
| - fが生成するキーとbの検索キーを比較して等しいものが見つかるかどうか二分探索する。 | ||
| https://github.com/t9a-dev/LeetCode_arai60/pull/42/changes/BASE..ba5b009c3fd1c49d89c5d2e1c2c09e7302f3b7d4#r2650867711 | ||
| ここでもらったコメントの理解が進んだ気がする。 | ||
|
|
||
| 所感 | ||
| https://doc.rust-lang.org/src/core/slice/mod.rs.html#2932-2934 | ||
| - binary_search_byの実装を見てみたら配列の境界にかなり気を使っている様子がコメントから伺えて面白かった。 | ||
| - 戻り値のインデックスを返す前にインデックスが範囲内であるかどうかを判定しているコードがある。 | ||
| https://doc.rust-lang.org/src/core/slice/mod.rs.html#2975 | ||
| - コンパイラに対してインデックスが範囲内であるという仮定を伝えている。 | ||
| https://doc.rust-lang.org/stable/std/hint/fn.assert_unchecked.html | ||
| - 最適化ヒントと呼ばれるものらしい。(C++日本語リファレンスより) | ||
| https://cpprefjp.github.io/lang/cpp23/portable_assumptions.html | ||
| */ | ||
|
|
||
| pub struct Solution {} | ||
| impl Solution { | ||
| pub fn search(nums: Vec<i32>, target: i32) -> i32 { | ||
| if nums.is_empty() { | ||
| return -1; | ||
| } | ||
|
|
||
| let last = nums.last().unwrap(); | ||
| //(bool ,i32) -> (targetが左右どちらの区間に属するか, 探している値) | ||
| let target_key = (target <= *last, target); | ||
|
|
||
| match nums.binary_search_by_key(&target_key, |num| (num <= last, *num)) { | ||
|
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. |num| (num <= last, *num) を target_key と合わせて二回書いている印象なので、一回変数においてはどうでしょうか。
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 search(nums: Vec<i32>, target: i32) -> i32 {
if nums.is_empty() {
return -1;
}
let last = *nums.last().unwrap();
let make_key = |x: i32| (x <= last, x);
match nums.binary_search_by_key(&make_key(target), |num| make_key(*num)) {
Ok(i) => i as i32,
Err(_) => -1,
}
} |
||
| Ok(i) => i as i32, | ||
| Err(_) => -1, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn step2a_test() { | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 0), 4); | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 3), -1); | ||
| assert_eq!(Solution::search(vec![1], 0), -1); | ||
| assert_eq!(Solution::search(vec![], 0), -1); | ||
|
|
||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 2), 6); | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 4), 0); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 1), 1); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 5), 0); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 3), 2); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // Step3 | ||
| // 目的: 覚えられないのは、なんか素直じゃないはずなので、そこを探し、ゴールに到達する | ||
|
|
||
| // 方法 | ||
| // 時間を測りながらもう一度解く | ||
| // 10分以内に一度もエラーを吐かず正解 | ||
| // これを3回連続でできたら終わり | ||
| // レビューを受ける | ||
| // 作れないデータ構造があった場合は別途自作すること | ||
|
|
||
| /* | ||
| n = nums.len() | ||
| 時間計算量: O(log n) | ||
| 空間計算量: O(1) | ||
| */ | ||
|
|
||
| /* | ||
| 1回目: 4分36秒 | ||
| 2回目: 3分21秒 | ||
| 3回目: 4分16秒 | ||
| */ | ||
|
|
||
| /* | ||
| 所感 | ||
| - コード量に対して時間がかかっている気がするが、暗記せず毎回ロジックを考える部分に時間がかかっているのでむしろ良い傾向だと思った。 | ||
| */ | ||
|
|
||
| pub struct Solution {} | ||
| impl Solution { | ||
| pub fn search(nums: Vec<i32>, target: i32) -> i32 { | ||
| if nums.is_empty() { | ||
| return -1; | ||
| } | ||
|
|
||
| let mut start = 0; | ||
| let mut end = nums.len(); | ||
| let last_num = *nums.last().unwrap(); | ||
|
|
||
| while start < end { | ||
| let middle = start + (end - start) / 2; | ||
|
|
||
| if last_num < target && nums[middle] <= last_num { | ||
| end = middle; | ||
| continue; | ||
| } | ||
|
|
||
| if target <= last_num && last_num < nums[middle] { | ||
| start = middle + 1; | ||
| continue; | ||
| } | ||
|
|
||
| if target <= nums[middle] { | ||
| end = middle; | ||
| } else { | ||
| start = middle + 1; | ||
| } | ||
| } | ||
|
|
||
| if end < nums.len() && nums[end] == target { | ||
| return end as i32; | ||
| } | ||
|
|
||
| -1 | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn step3_test() { | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 0), 4); | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 3), -1); | ||
| assert_eq!(Solution::search(vec![1], 0), -1); | ||
| assert_eq!(Solution::search(vec![], 0), -1); | ||
|
|
||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 2), 6); | ||
| assert_eq!(Solution::search(vec![4, 5, 6, 7, 0, 1, 2], 4), 0); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 5), 0); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 1), 1); | ||
| assert_eq!(Solution::search(vec![5, 1, 3], 3), 2); | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
step1はいわゆる探索範囲が閉区間[start, end]になっているタイプですが、ここのnums[middle] == targetの早期returnを外してstep2, 3のようにループ終了後に返り値を決める書き方に変えると、targetが配列numsの先頭にある場合などに、usize型のendで0 - 1を計算してオーバーフローする可能性がある、ということに注意が必要ですね。