回答編集履歴
4
めぐる式でコードを書き直す
test
CHANGED
@@ -161,3 +161,45 @@
|
|
161
161
|
return [left, right] if left <= right else [-1, -1]
|
162
162
|
|
163
163
|
```
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
---
|
168
|
+
|
169
|
+
[二分探索アルゴリズムを一般化 〜 めぐる式二分探索法のススメ 〜](https://qiita.com/drken/items/97e37dd6143e33a64c8c)を読んで、やっとめぐる式の利点に気づいたので、今更ながら追記。
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
めぐる式で重要なのは、`solve`とそれが満たされる端`ok`、満たされない端`ng`さえ決まれば、あとは同じ書き方で済む点。半開区間か開区間かというのは重要ではなく、解きたい問題によって使い分ければいい。
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
今回の問題では、開始位置は`nums[i] >= target`が満たされる最小の`i`により、終了位置は`nums[i] <= target`が満たされる最大の`i`により求まるので、このように書ける。
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
```python
|
182
|
+
|
183
|
+
class Solution:
|
184
|
+
|
185
|
+
def searchRange(self, nums: List[int], target: int) -> List[int]:
|
186
|
+
|
187
|
+
def meguru(ok, ng, solve):
|
188
|
+
|
189
|
+
while abs(ok - ng) > 1:
|
190
|
+
|
191
|
+
mid = (ok + ng) // 2
|
192
|
+
|
193
|
+
if solve(mid): ok = mid
|
194
|
+
|
195
|
+
else: ng = mid
|
196
|
+
|
197
|
+
return ok
|
198
|
+
|
199
|
+
left = meguru(len(nums), -1, lambda i: nums[i] >= target)
|
200
|
+
|
201
|
+
right = meguru(-1, len(nums), lambda i: nums[i] <= target)
|
202
|
+
|
203
|
+
return [left, right] if left <= right else [-1, -1]
|
204
|
+
|
205
|
+
```
|
3
範囲の記述修正
test
CHANGED
@@ -16,7 +16,7 @@
|
|
16
16
|
|
17
17
|
`left`も`right`も`nums`の添え字として有効な値となっているので、これは「閉区間」です。
|
18
18
|
|
19
|
-
「閉区間」で`mid`を+1,-1しない実装だと、`left`と`right`
|
19
|
+
「閉区間」で`mid`を+1,-1しない実装だと、`left`と`right`に挟まれる範囲が2未満にならない場合があります。
|
20
20
|
|
21
21
|
そうなると、ループを抜けた後で`left`,`right`両方で条件判定して、正しい境界を見つけなければなりません。
|
22
22
|
|
2
元のソースの修正例を追加
test
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
めぐる式
|
1
|
+
めぐる式が「半開区間」を前提としているのに、質問者さんのコードが「閉区間」で実装されているのがまずいのではないでしょうか。
|
2
2
|
|
3
3
|
|
4
4
|
|
@@ -20,11 +20,103 @@
|
|
20
20
|
|
21
21
|
そうなると、ループを抜けた後で`left`,`right`両方で条件判定して、正しい境界を見つけなければなりません。
|
22
22
|
|
23
|
+
こちらの方針で質問者さんのコードを修正すると、以下のようになります。
|
24
|
+
|
25
|
+
```python
|
26
|
+
|
27
|
+
class Solution:
|
28
|
+
|
29
|
+
def searchRange(self, nums: List[int], target: int) -> List[int]:
|
30
|
+
|
31
|
+
if not nums:
|
32
|
+
|
33
|
+
return [-1, -1]
|
34
|
+
|
35
|
+
# find end
|
36
|
+
|
37
|
+
left = 0
|
38
|
+
|
39
|
+
right = len(nums) - 1
|
40
|
+
|
41
|
+
while right - left > 1:
|
42
|
+
|
43
|
+
mid = left + ((right - left) // 2)
|
44
|
+
|
45
|
+
if self.isin_left_area(nums[mid], target):
|
46
|
+
|
47
|
+
right = mid
|
48
|
+
|
49
|
+
else:
|
50
|
+
|
51
|
+
left = mid
|
52
|
+
|
53
|
+
if self.isin_left_area(nums[left], target):
|
54
|
+
|
55
|
+
end = left - 1
|
56
|
+
|
57
|
+
elif self.isin_left_area(nums[right], target):
|
58
|
+
|
59
|
+
end = left
|
60
|
+
|
61
|
+
else:
|
62
|
+
|
63
|
+
end = right
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
# find start
|
68
|
+
|
69
|
+
left = 0
|
70
|
+
|
71
|
+
right = len(nums) - 1
|
72
|
+
|
73
|
+
while right - left > 1:
|
74
|
+
|
75
|
+
mid = left + ((right - left) // 2)
|
76
|
+
|
77
|
+
if self.isin_right_area(nums[mid], target):
|
78
|
+
|
79
|
+
left = mid
|
80
|
+
|
81
|
+
else:
|
82
|
+
|
83
|
+
right = mid
|
84
|
+
|
85
|
+
if self.isin_right_area(nums[right], target):
|
86
|
+
|
87
|
+
start = right + 1
|
88
|
+
|
89
|
+
elif self.isin_right_area(nums[left], target):
|
90
|
+
|
91
|
+
start = right
|
92
|
+
|
93
|
+
else:
|
94
|
+
|
95
|
+
start = left
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
return [start, end] if start <= end else [-1, -1]
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
def isin_left_area(self, mid_val:int, target:int):
|
104
|
+
|
105
|
+
return mid_val > target
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
def isin_right_area(self, mid_val:int, target:int):
|
110
|
+
|
111
|
+
return mid_val < target
|
112
|
+
|
113
|
+
```
|
114
|
+
|
23
115
|
|
24
116
|
|
25
117
|
めぐる式の「半開区間」でも、ループを抜けた後の条件判定が一度は必要になるので、めぐる式のベースである蟻本で採用している「開区間」を採用する方が、正直なところ実装は楽だと思います。
|
26
118
|
|
27
|
-
`left`、`right`とも`nums`の添え字の範囲外であることに注意してください。
|
119
|
+
以下のソースで、`left`、`right`とも`nums`の添え字の範囲外であることに注意してください。
|
28
120
|
|
29
121
|
```python
|
30
122
|
|
1
区間の説明
test
CHANGED
@@ -1,4 +1,30 @@
|
|
1
|
-
めぐる式
|
1
|
+
めぐる式は「半開区間」を前提としていますが、質問者さんのコードは「閉区間」で実装されているのがまずいのではないでしょうか。
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
質問者さんのコードだと、以下のように範囲をとっています。
|
6
|
+
|
7
|
+
```python
|
8
|
+
|
9
|
+
left = 0
|
10
|
+
|
11
|
+
right = len(nums) - 1
|
12
|
+
|
13
|
+
```
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
`left`も`right`も`nums`の添え字として有効な値となっているので、これは「閉区間」です。
|
18
|
+
|
19
|
+
「閉区間」で`mid`を+1,-1しない実装だと、`left`と`right`の差が1未満にならない場合があります。
|
20
|
+
|
21
|
+
そうなると、ループを抜けた後で`left`,`right`両方で条件判定して、正しい境界を見つけなければなりません。
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
めぐる式の「半開区間」でも、ループを抜けた後の条件判定が一度は必要になるので、めぐる式のベースである蟻本で採用している「開区間」を採用する方が、正直なところ実装は楽だと思います。
|
26
|
+
|
27
|
+
`left`、`right`とも`nums`の添え字の範囲外であることに注意してください。
|
2
28
|
|
3
29
|
```python
|
4
30
|
|