回答編集履歴

4

めぐる式でコードを書き直す

2021/10/21 13:07

投稿

actorbug
actorbug

スコア2431

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

範囲の記述修正

2021/10/21 13:06

投稿

actorbug
actorbug

スコア2431

test CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  `left`も`right`も`nums`の添え字として有効な値となっているので、これは「閉区間」です。
18
18
 
19
- 「閉区間」で`mid`を+1,-1しない実装だと、`left`と`right`の差1未満にならない場合があります。
19
+ 「閉区間」で`mid`を+1,-1しない実装だと、`left`と`right`に挟まれる範囲2未満にならない場合があります。
20
20
 
21
21
  そうなると、ループを抜けた後で`left`,`right`両方で条件判定して、正しい境界を見つけなければなりません。
22
22
 

2

元のソースの修正例を追加

2021/10/17 08:14

投稿

actorbug
actorbug

スコア2431

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

区間の説明

2021/10/17 08:08

投稿

actorbug
actorbug

スコア2431

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