From 1677b6114d192e9214b6066a32bf0c0238034b29 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Thu, 26 Jun 2025 18:17:22 +0900 Subject: [PATCH 1/4] Update README template to add placeholders for solution steps --- README_template.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README_template.md b/README_template.md index 1ecb00e..e19cb7b 100644 --- a/README_template.md +++ b/README_template.md @@ -9,13 +9,25 @@ Python # 自分の解法 +## step1 + +```python + +``` + - 時間計算量:`O(n)` - 空間計算量:`O(n)` ## step2 +```python + +``` + ## step3 +## step4 (FB) + # 別解・模範解答 - 時間計算量:`O(n)` From fad236878044909d10f0896a8a90a83e5866e3d3 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Thu, 14 Aug 2025 23:38:09 +0900 Subject: [PATCH 2/4] Solve 33_search_in_rotated_sorted_array_medium --- .../README.md | 191 ++++++++++++++++++ .../step1.py | 51 +++++ .../step2.py | 34 ++++ .../step2_exclusive_range.py | 45 +++++ .../step3.py | 37 ++++ 5 files changed, 358 insertions(+) create mode 100644 33_search_in_rotated_sorted_array_medium/README.md create mode 100644 33_search_in_rotated_sorted_array_medium/step1.py create mode 100644 33_search_in_rotated_sorted_array_medium/step2.py create mode 100644 33_search_in_rotated_sorted_array_medium/step2_exclusive_range.py create mode 100644 33_search_in_rotated_sorted_array_medium/step3.py diff --git a/33_search_in_rotated_sorted_array_medium/README.md b/33_search_in_rotated_sorted_array_medium/README.md new file mode 100644 index 0000000..ac0f1ca --- /dev/null +++ b/33_search_in_rotated_sorted_array_medium/README.md @@ -0,0 +1,191 @@ +# 問題へのリンク + + +# 言語 +Python + +# 問題の概要 +rotateされたソート済み配列から特定の値`target`を検索する問題です。配列は回転されているため、通常の二分探索ではなく、回転された状態を考慮した二分探索を行う必要があります。 + + +# 自分の解法 + +## step1 + +2回二分探索を用いて、回転されたソート済み配列から特定の値を検索する解法。1回目の二分探索で、配列の最小値を見つけ、`shift`(どのくらい配列がrotateされたか) の値を求める。2回目の二分探索で、`shift`を考慮して元の配列のインデックスを計算し、通常の二分探索で`target`を検索する。インデックスの計算が毎回必要になるため、少し複雑な実装になる。 +- 時間計算量:`O(logn)` +- 空間計算量:`O(1)` + +```python +class Solution: + def search(self, nums: list[int], target: int) -> int: + def find_rotated_shift() -> int: + left = 0 + right = len(nums) - 1 + if nums[left] <= nums[right]: + return 0 + while (right - left) > 1: + mid = (right + left) // 2 + if nums[mid] <= nums[right]: + right = mid + else: + left = mid + return right + + def shifted_index(index: int, shift: int) -> int: + return (index + shift) % len(nums) + + NOT_FOUND = -1 + + shift = find_rotated_shift() + left = 0 + right = len(nums) - 1 + if target < nums[shifted_index(left, shift)]: + return NOT_FOUND + elif target > nums[shifted_index(right, shift)]: + return NOT_FOUND + elif nums[shifted_index(right, shift)] == target: + return shifted_index(right, shift) + + mid = 0 + while (right - left) > 1: + mid = (right + left) // 2 + if target < nums[shifted_index(mid, shift)]: + right = mid + else: + left = mid + if nums[shifted_index(left, shift)] == target: + return shifted_index(left, shift) + else: + return NOT_FOUND +``` + + +## step2 + +```python +class Solution: + def search(self, nums: list[int], target: int) -> int: + left = 0 + right = len(nums) - 1 + while left <= right: + mid = (right + left) // 2 + if nums[mid] == target: + return mid + # midが左側の領域にいる + if nums[left] <= nums[mid]: + if nums[left] <= target < nums[mid]: + right = mid - 1 + else: + left = mid + 1 + + # midが右側の領域にいる + else: + if nums[mid] < target <= nums[right]: + left = mid + 1 + else: + right = mid - 1 + + NOT_FOUND = -1 + return NOT_FOUND +``` + +- 模範解答を見て二分探索を1回だけ用いる解法(ワンパス二分探索)に変更した。条件分岐が複雑で、二回に分けて考える解法の方がわかりやすいのでは?とも考えたが、次のように考えるとこの場合分けが漏れのないものだと自信を持てるようになった。 +- 原則:「回転した配列を真ん中(`mid`)で分割すると、`[left,mid]`もしくは`[mid,right]`のいずれか一方はソートされた配列になっている。」 +- 場合1:`nums[left] <= nums[mid]` の場合、左側の部分配列はソートされている。 + - この場合、`target`が左側にあるかどうかを確認し、そうであれば左側を探索する。もしそうでなければ左側はもう探索する必要がないので、この範囲を捨て、右側を探索するようにする。 +- 場合2:`nums[left] > nums[mid]` の場合、逆に`nums[mid]<=nums[right]`が成り立ち、右側の部分配列はソートされている。 + - この場合、`target`が右側にあるかどうかを確認し、もしそうであれば右側を探索する。そうでなければ右側はもう探索する必要がないので、この範囲を捨て、左側を探索するようにする。 +- 本問では`target`に一致するindexを探すので、添え字の区間を`[left,mid)`,`[mid,mid]`,`(mid,right]`の3つに分けて考えるとわかりやすい。 +- 基本的には閉区間でインデックスを指す二分探索がわかりやすいと感じている。 +- `while left <= right`を使った二分探索は初めて。 + - `while left <= right`: **特定の値を「見つける」** ための探索。ループの **中で** 答えが見つかる。答えが見つかり次第、値を返すので、ループを走査し終えたときは答えが見つからなかったことを意味する。 + - `while left < right`: **条件を満たす「境界」**(例: 挿入位置、最初のtrueなど)を探すための探索。ループが **終わった後** に答えが確定する。 + - これまでは境界を求めるために `while (right-left)>1` という条件で探索を行っていたが、これでは前処理が少し複雑になる。`while left < right` に変更することで、よりシンプルに実装できるようになるが、`left` と `right`の更新が`mid`, `mid+1`, `mid-1`のいずれかになることに気をつける必要がある。(`while (right-left)>1`の場合は、`mid`だけで更新できる)→めぐる式を採用すれば解決しそうだが、今度は読み手に伝わるかどうかが問題となる。 + + +## step3 + +```python +class Solution: + def search(self, nums: list[int], target: int) -> int: + left = 0 + right = len(nums) - 1 + while left <= right: + mid = (right + left) // 2 + if nums[mid] == target: + return mid + + # [left, mid]がソート済み配列の場合 + if nums[left] <= nums[mid]: + # target が [left, mid]の範囲内にある場合 + if nums[left] <= target < nums[mid]: + right = mid - 1 + # それ以外([left, mid]の範囲を捨てる) + else: + left = mid + 1 + # [mid, right]がソート済み配列の場合 + else: + # target が [mid,right]の範囲内にある場合 + if nums[mid] < target <= nums[right]: + left = mid + 1 + # それ以外([mid,right]の範囲を捨てる) + else: + right = mid - 1 + NOT_FOUND = -1 + return NOT_FOUND +``` + +- 時間計算量:`O(logn)` +- 空間計算量:`O(1)` + +# 別解 区間を分ける +```python +class Solution: + def search(self, nums: list[int], target: int) -> int: + # 最小値のインデックスを求める + def find_min_index(nums): + left = 0 + right = len(nums) - 1 + while left < right: + mid = (left + right) // 2 + if nums[mid] > nums[right]: + left = mid + 1 + else: + right = mid + return left + + min_index = find_min_index(nums) + + # min_indexを基準に2つの区間に分けて探索 + left, right = 0, len(nums) - 1 + while left <= right: + mid = (left + right) // 2 + if nums[mid] == target: + return mid + # 左側の区間を探索 + if mid < min_index: + if nums[left] <= target < nums[mid]: + right = mid - 1 + else: + left = mid + 1 + # 右側の区間を探索 + else: + if nums[mid] < target <= nums[right]: + left = mid + 1 + else: + right = mid - 1 + + NOT_FOUND = -1 + return NOT_FOUND +``` + +- こちらも二分探索を2回用いる解法である。最初に最小値のインデックス`min_index`を求めた後、`min_index`を基準にして2つの区間に分けて探索を行う。 +- 具体的には、`nums[:min_index]`と`nums[min_index:]`の2つの部分配列に分けて、それぞれに対して通常の二分探索を行う。 + - スライスを作るとメモリを消費するので、インデックスで計算を進める。 + +- 時間計算量:`O(logn)` +- 空間計算量:`O(1)` + +# 次に解く問題の予告 +- diff --git a/33_search_in_rotated_sorted_array_medium/step1.py b/33_search_in_rotated_sorted_array_medium/step1.py new file mode 100644 index 0000000..7a1f19f --- /dev/null +++ b/33_search_in_rotated_sorted_array_medium/step1.py @@ -0,0 +1,51 @@ +# +# @lc app=leetcode id=33 lang=python3 +# +# [33] Search in Rotated Sorted Array +# + +# @lc code=start +class Solution: + def search(self, nums: list[int], target: int) -> int: + def find_rotated_shift() -> int: + left = 0 + right = len(nums) - 1 + if nums[left] <= nums[right]: + return 0 + while (right - left) > 1: + mid = (right + left) // 2 + if nums[mid] <= nums[right]: + right = mid + else: + left = mid + return right + + def shifted_index(index: int, shift: int) -> int: + return (index + shift) % len(nums) + + NOT_FOUND = -1 + + shift = find_rotated_shift() + left = 0 + right = len(nums) - 1 + if target < nums[shifted_index(left, shift)]: + return NOT_FOUND + elif target > nums[shifted_index(right, shift)]: + return NOT_FOUND + elif nums[shifted_index(right, shift)] == target: + return shifted_index(right, shift) + + mid = 0 + while (right - left) > 1: + mid = (right + left) // 2 + if target < nums[shifted_index(mid, shift)]: + right = mid + else: + left = mid + if nums[shifted_index(left, shift)] == target: + return shifted_index(left, shift) + else: + return NOT_FOUND + + +# @lc code=end diff --git a/33_search_in_rotated_sorted_array_medium/step2.py b/33_search_in_rotated_sorted_array_medium/step2.py new file mode 100644 index 0000000..5d49777 --- /dev/null +++ b/33_search_in_rotated_sorted_array_medium/step2.py @@ -0,0 +1,34 @@ +# +# @lc app=leetcode id=33 lang=python3 +# +# [33] Search in Rotated Sorted Array +# + +# @lc code=start +class Solution: + def search(self, nums: list[int], target: int) -> int: + left = 0 + right = len(nums) - 1 + while left <= right: + mid = (right + left) // 2 + if nums[mid] == target: + return mid + # midが左側の領域にいる + if nums[left] <= nums[mid]: + if nums[left] <= target < nums[mid]: + right = mid - 1 + else: + left = mid + 1 + + # midが右側の領域にいる + else: + if nums[mid] < target <= nums[right]: + left = mid + 1 + else: + right = mid - 1 + + NOT_FOUND = -1 + return NOT_FOUND + + +# @lc code=end diff --git a/33_search_in_rotated_sorted_array_medium/step2_exclusive_range.py b/33_search_in_rotated_sorted_array_medium/step2_exclusive_range.py new file mode 100644 index 0000000..e9cbc22 --- /dev/null +++ b/33_search_in_rotated_sorted_array_medium/step2_exclusive_range.py @@ -0,0 +1,45 @@ +# +# @lc app=leetcode id=33 lang=python3 +# +# [33] Search in Rotated Sorted Array +# + +# @lc code=start +class Solution: + def search(self, nums: list[int], target: int) -> int: + def find_min_index() -> int: + left = 0 + right = len(nums) - 1 + + while left < right: + mid = (right + left) // 2 + if nums[mid] > nums[right]: + left = mid + 1 + else: + right = mid + return left + + def binary_search(left_bound, right_bound, target) -> int: + left = left_bound + right = right_bound + + while left <= right: + mid = (right + left) // 2 + if nums[mid] == target: + return mid + if target < nums[mid]: + right = mid - 1 + else: + left = mid + 1 + return NOT_FOUND + + NOT_FOUND = -1 + min_idx = find_min_index() + + if nums[min_idx] <= target <= nums[-1]: + return binary_search(min_idx, len(nums) - 1, target) + else: + return binary_search(0, min_idx - 1, target) + + +# @lc code=end diff --git a/33_search_in_rotated_sorted_array_medium/step3.py b/33_search_in_rotated_sorted_array_medium/step3.py new file mode 100644 index 0000000..0562acd --- /dev/null +++ b/33_search_in_rotated_sorted_array_medium/step3.py @@ -0,0 +1,37 @@ +# +# @lc app=leetcode id=33 lang=python3 +# +# [33] Search in Rotated Sorted Array +# + +# @lc code=start +class Solution: + def search(self, nums: list[int], target: int) -> int: + left = 0 + right = len(nums) - 1 + while left <= right: + mid = (right + left) // 2 + if nums[mid] == target: + return mid + + # [left, mid]がソート済み配列の場合 + if nums[left] <= nums[mid]: + # target が [left, mid]の範囲内にある場合 + if nums[left] <= target < nums[mid]: + right = mid - 1 + # それ以外([left, mid]の範囲を捨てる) + else: + left = mid + 1 + # [mid, right]がソート済み配列の場合 + else: + # target が [mid,right]の範囲内にある場合 + if nums[mid] < target <= nums[right]: + left = mid + 1 + # それ以外([mid,right]の範囲を捨てる) + else: + right = mid - 1 + NOT_FOUND = -1 + return NOT_FOUND + + +# @lc code=end From 2a0f57fdbfa68fa6fdfdc27c029f4e782f7ae896 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Thu, 14 Aug 2025 23:42:07 +0900 Subject: [PATCH 3/4] Add next problem preview for Coin Change in README --- 33_search_in_rotated_sorted_array_medium/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/33_search_in_rotated_sorted_array_medium/README.md b/33_search_in_rotated_sorted_array_medium/README.md index ac0f1ca..83e4d27 100644 --- a/33_search_in_rotated_sorted_array_medium/README.md +++ b/33_search_in_rotated_sorted_array_medium/README.md @@ -188,4 +188,4 @@ class Solution: - 空間計算量:`O(1)` # 次に解く問題の予告 -- +- [Coin Change - LeetCode](https://leetcode.com/problems/coin-change/) From c3aa445b55cc0f5d0dcef2fa6aa3b795684c9e3b Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Sun, 17 Aug 2025 10:08:42 +0900 Subject: [PATCH 4/4] Fix link formatting for problem reference in README --- 33_search_in_rotated_sorted_array_medium/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/33_search_in_rotated_sorted_array_medium/README.md b/33_search_in_rotated_sorted_array_medium/README.md index 83e4d27..c5ecba3 100644 --- a/33_search_in_rotated_sorted_array_medium/README.md +++ b/33_search_in_rotated_sorted_array_medium/README.md @@ -1,5 +1,5 @@ # 問題へのリンク - +[Search in Rotated Sorted Array - LeetCode](https://leetcode.com/problems/search-in-rotated-sorted-array/) # 言語 Python