回答編集履歴
4
めぐる式でコードを書き直す
answer
CHANGED
|
@@ -79,4 +79,25 @@
|
|
|
79
79
|
|
|
80
80
|
left, right = binarySearchLeft(nums, target), binarySearchRight(nums, target)
|
|
81
81
|
return [left, right] if left <= right else [-1, -1]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
[二分探索アルゴリズムを一般化 〜 めぐる式二分探索法のススメ 〜](https://qiita.com/drken/items/97e37dd6143e33a64c8c)を読んで、やっとめぐる式の利点に気づいたので、今更ながら追記。
|
|
86
|
+
|
|
87
|
+
めぐる式で重要なのは、`solve`とそれが満たされる端`ok`、満たされない端`ng`さえ決まれば、あとは同じ書き方で済む点。半開区間か開区間かというのは重要ではなく、解きたい問題によって使い分ければいい。
|
|
88
|
+
|
|
89
|
+
今回の問題では、開始位置は`nums[i] >= target`が満たされる最小の`i`により、終了位置は`nums[i] <= target`が満たされる最大の`i`により求まるので、このように書ける。
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
class Solution:
|
|
93
|
+
def searchRange(self, nums: List[int], target: int) -> List[int]:
|
|
94
|
+
def meguru(ok, ng, solve):
|
|
95
|
+
while abs(ok - ng) > 1:
|
|
96
|
+
mid = (ok + ng) // 2
|
|
97
|
+
if solve(mid): ok = mid
|
|
98
|
+
else: ng = mid
|
|
99
|
+
return ok
|
|
100
|
+
left = meguru(len(nums), -1, lambda i: nums[i] >= target)
|
|
101
|
+
right = meguru(-1, len(nums), lambda i: nums[i] <= target)
|
|
102
|
+
return [left, right] if left <= right else [-1, -1]
|
|
82
103
|
```
|
3
範囲の記述修正
answer
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
`left`も`right`も`nums`の添え字として有効な値となっているので、これは「閉区間」です。
|
|
10
|
-
「閉区間」で`mid`を+1,-1しない実装だと、`left`と`right`
|
|
10
|
+
「閉区間」で`mid`を+1,-1しない実装だと、`left`と`right`に挟まれる範囲が2未満にならない場合があります。
|
|
11
11
|
そうなると、ループを抜けた後で`left`,`right`両方で条件判定して、正しい境界を見つけなければなりません。
|
|
12
12
|
こちらの方針で質問者さんのコードを修正すると、以下のようになります。
|
|
13
13
|
```python
|
2
元のソースの修正例を追加
answer
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
めぐる式
|
|
1
|
+
めぐる式が「半開区間」を前提としているのに、質問者さんのコードが「閉区間」で実装されているのがまずいのではないでしょうか。
|
|
2
2
|
|
|
3
3
|
質問者さんのコードだと、以下のように範囲をとっています。
|
|
4
4
|
```python
|
|
@@ -9,9 +9,55 @@
|
|
|
9
9
|
`left`も`right`も`nums`の添え字として有効な値となっているので、これは「閉区間」です。
|
|
10
10
|
「閉区間」で`mid`を+1,-1しない実装だと、`left`と`right`の差が1未満にならない場合があります。
|
|
11
11
|
そうなると、ループを抜けた後で`left`,`right`両方で条件判定して、正しい境界を見つけなければなりません。
|
|
12
|
+
こちらの方針で質問者さんのコードを修正すると、以下のようになります。
|
|
13
|
+
```python
|
|
14
|
+
class Solution:
|
|
15
|
+
def searchRange(self, nums: List[int], target: int) -> List[int]:
|
|
16
|
+
if not nums:
|
|
17
|
+
return [-1, -1]
|
|
18
|
+
# find end
|
|
19
|
+
left = 0
|
|
20
|
+
right = len(nums) - 1
|
|
21
|
+
while right - left > 1:
|
|
22
|
+
mid = left + ((right - left) // 2)
|
|
23
|
+
if self.isin_left_area(nums[mid], target):
|
|
24
|
+
right = mid
|
|
25
|
+
else:
|
|
26
|
+
left = mid
|
|
27
|
+
if self.isin_left_area(nums[left], target):
|
|
28
|
+
end = left - 1
|
|
29
|
+
elif self.isin_left_area(nums[right], target):
|
|
30
|
+
end = left
|
|
31
|
+
else:
|
|
32
|
+
end = right
|
|
12
33
|
|
|
34
|
+
# find start
|
|
35
|
+
left = 0
|
|
36
|
+
right = len(nums) - 1
|
|
37
|
+
while right - left > 1:
|
|
38
|
+
mid = left + ((right - left) // 2)
|
|
39
|
+
if self.isin_right_area(nums[mid], target):
|
|
40
|
+
left = mid
|
|
41
|
+
else:
|
|
42
|
+
right = mid
|
|
43
|
+
if self.isin_right_area(nums[right], target):
|
|
44
|
+
start = right + 1
|
|
45
|
+
elif self.isin_right_area(nums[left], target):
|
|
46
|
+
start = right
|
|
47
|
+
else:
|
|
48
|
+
start = left
|
|
49
|
+
|
|
50
|
+
return [start, end] if start <= end else [-1, -1]
|
|
51
|
+
|
|
52
|
+
def isin_left_area(self, mid_val:int, target:int):
|
|
53
|
+
return mid_val > target
|
|
54
|
+
|
|
55
|
+
def isin_right_area(self, mid_val:int, target:int):
|
|
56
|
+
return mid_val < target
|
|
57
|
+
```
|
|
58
|
+
|
|
13
59
|
めぐる式の「半開区間」でも、ループを抜けた後の条件判定が一度は必要になるので、めぐる式のベースである蟻本で採用している「開区間」を採用する方が、正直なところ実装は楽だと思います。
|
|
14
|
-
`left`、`right`とも`nums`の添え字の範囲外であることに注意してください。
|
|
60
|
+
以下のソースで、`left`、`right`とも`nums`の添え字の範囲外であることに注意してください。
|
|
15
61
|
```python
|
|
16
62
|
class Solution:
|
|
17
63
|
def searchRange(self, nums, target):
|
1
区間の説明
answer
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
めぐる式
|
|
1
|
+
めぐる式は「半開区間」を前提としていますが、質問者さんのコードは「閉区間」で実装されているのがまずいのではないでしょうか。
|
|
2
|
+
|
|
3
|
+
質問者さんのコードだと、以下のように範囲をとっています。
|
|
2
4
|
```python
|
|
5
|
+
left = 0
|
|
6
|
+
right = len(nums) - 1
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`left`も`right`も`nums`の添え字として有効な値となっているので、これは「閉区間」です。
|
|
10
|
+
「閉区間」で`mid`を+1,-1しない実装だと、`left`と`right`の差が1未満にならない場合があります。
|
|
11
|
+
そうなると、ループを抜けた後で`left`,`right`両方で条件判定して、正しい境界を見つけなければなりません。
|
|
12
|
+
|
|
13
|
+
めぐる式の「半開区間」でも、ループを抜けた後の条件判定が一度は必要になるので、めぐる式のベースである蟻本で採用している「開区間」を採用する方が、正直なところ実装は楽だと思います。
|
|
14
|
+
`left`、`right`とも`nums`の添え字の範囲外であることに注意してください。
|
|
15
|
+
```python
|
|
3
16
|
class Solution:
|
|
4
17
|
def searchRange(self, nums, target):
|
|
5
18
|
def binarySearchLeft(A, x):
|