目次
トピックのソース
34. ソートされた配列内の要素の最初と最後の位置を見つける - LeetCode
トピックの説明
降順でない整数配列 nums と目標値 target が与えられます。指定されたターゲット値が配列内のどこで始まり、どこで終わるかを調べてください。
ターゲット値 target が配列内に存在しない場合は、[-1, -1] を返します。
この問題を解決するには、時間計算量 O(log n) のアルゴリズムを設計して実装する必要があります。
例
入力 | 数値 = [5,7,7,8,8,10]、ターゲット = 8 |
出力 | [3,4] |
説明する | なし |
入力 | 数値 = [5,7,7,8,8,10]、ターゲット = 6 |
出力 | [-1、-1] |
説明する | なし |
入力 | 数値 = []、ターゲット = 0 |
出力 | [-1、-1] |
説明する | なし |
ヒント
- 0 <= nums.length <= 105
- -109 <= nums[i] <= 109
- nums は減少しない配列です
- -109 <= ターゲット <= 109
トピック分析
この質問の nums は、昇順に並べられた整数配列 nums です。つまり、nums には繰り返しの値があるため、この質問で検索される要素には最初と最後の位置の概念があります。
この問題では、時間計算量 O(logN) 以内に要素の最初と最後の位置の検索を完了する必要があります。この質問の数は単調であるため、二分探索を考えるのは簡単ですが、時間計算量はバイナリも O (logN)
ただし、Java 言語の Arrays.binarySearch など、一般的に使用される二分検索は、値が重複する要素の位置を見つけるのには適していません。たとえば、次のコードで検索される要素 1 の位置は、最終的に次のように返されます。インデックス2。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] arr = {1, 1, 1, 1, 1};
System.out.println(Arrays.binarySearch(arr, 1));
}
}
これは、基礎となる二分探索戦略に関連しています。二分探索については、このブログで二分探索の実装を読むことができます。
アルゴリズム設計 - 二分法と三分法、羅谷 P3382 - 府城市外のブログ - CSDN ブログ
3 つの言語の標準バイナリ検索実装を以下に示します。
Java標準二分探索実装
import java.util.*;
public class Main {
public static void main(String[] args) {
int[] arr = {1,1,1,1,1};
System.out.println(binarySearch(arr, 1));
}
public static int binarySearch(int[] arr, int target) {
int low = 0;
int high = arr.length - 1;
while (low <= high) {
int mid = (low + high) >> 1;
int midVal = arr[mid];
if (midVal > target) {
high = mid - 1;
} else if (midVal < target) {
low = mid + 1;
} else {
return mid; // midVal == target,则直接返回mid作为target的位置
}
}
return -low - 1; // 找不到则返回插入位置
}
}
JavaScript 標準バイナリ検索の実装
function binarySearch(arr, target) {
let low = 0;
let high = arr.length - 1;
while (low <= high) {
const mid = (low + high) >> 1;
const midVal = arr[mid];
if (midVal > target) {
high = mid - 1;
} else if (midVal < target) {
low = mid + 1;
} else {
return mid; // midVal == target,则直接返回mid作为target的位置
}
}
return -low - 1; // 找不到则返回插入位置
}
Python バイナリ検索の標準実装
def binarySearch(arr, target):
low = 0
high = len(arr) - 1
while low <= high:
mid = (low + high) >> 1
midVal = arr[mid]
if midVal > target:
high = mid - 1
elif midVal < target:
low = mid + 1
else:
return mid
return -low - 1
標準的な二分探索フレームワークでは、midVal == targetの場合、そのときのmid値がそのままターゲットの位置として返されます。
ただし、ターゲットに値が繰り返される複数の要素がある場合は、midVal == target の場合、mid がターゲットの位置であると恣意的に決定するのではなく、mid の位置の左右に判断を拡張するようにしてください。
判定を中間位置の左側に拡張します。
- Mid == 0 または arr[mid] != arr[mid-1] の場合、現在の Mid 位置がすでにターゲット番号フィールドの左境界、つまりターゲットが初めて表示される位置であることを意味します。
- Mid > 0 かつ arr[mid] == arr[mid - 1] の場合、ターゲット番号フィールドの左境界がまだ中間位置の左側にあることを意味します。 、高 = 中 - 1 に設定する必要があります。
判定を中間位置の右側に拡張します。
- Mid == arr.length - 1 または arr[mid] != arr[mid + 1] の場合、現在の Mid 位置がすでにターゲット番号フィールドの右境界、つまりターゲットが出現した位置であることを意味します最後
- Mid < nums.length -1 かつ arr[mid] == arr[mid+1] の場合、ターゲット番号フィールドの右境界がまだ中間位置の右側にあることを意味します。正しい境界を見つけるには、low = middle + 1 とする必要があります。
実装コードは次のとおりです。
Java実装コード
import java.util.*;
public class Main {
public static void main(String[] args) {
int[] arr = {1,1,1,1,1};
System.out.println(binarySearch(arr, 1));
}
public static int searchFirst(int[] arr, int target) {
int low = 0;
int high = arr.length - 1;
while (low <= high) {
int mid = (low + high) >> 1;
int midVal = arr[mid];
if (midVal > target) {
high = mid - 1;
} else if (midVal < target) {
low = mid + 1;
} else {
// 向左延伸判断,mid是否为target数域的左边界,即第一次出现的位置
if(mid == 0 || arr[mid] != arr[mid - 1]) {
return mid;
} else {
high = mid - 1;
}
}
}
return -low - 1; // 找不到则返回插入位置
}
public static int searchLast(int[] arr, int target) {
int low = 0;
int high = arr.length - 1;
while (low <= high) {
int mid = (low + high) >> 1;
int midVal = arr[mid];
if (midVal > target) {
high = mid - 1;
} else if (midVal < target) {
low = mid + 1;
} else {
// 向右延伸判断,mid是否为target数域的右边界,即最后一次出现的位置
if(mid == nums.length - 1 || arr[mid] != arr[mid + 1]) {
return mid;
} else {
low = mid + 1;
}
}
}
return -low - 1; // 找不到则返回插入位置
}
}
JavaScript 実装コード
function searchFirst(arr, target) {
let low = 0;
let high = arr.length - 1;
while (low <= high) {
const mid = (low + high) >> 1;
const midVal = arr[mid];
if (midVal > target) {
high = mid - 1;
} else if (midVal < target) {
low = mid + 1;
} else {
// 向左延伸判断,mid是否为target数域的左边界,即第一次出现的位置
if (mid == 0 || arr[mid] != arr[mid - 1]) {
return mid;
} else {
high = mid - 1;
}
}
}
return -low - 1; // 找不到则返回插入位置
}
function searchLast(arr, target) {
let low = 0;
let high = arr.length - 1;
while (low <= high) {
const mid = (low + high) >> 1;
const midVal = arr[mid];
if (midVal > target) {
high = mid - 1;
} else if (midVal < target) {
low = mid + 1;
} else {
// 向右延伸判断,mid是否为target数域的右边界,即最后一次出现的位置
if (mid == arr.length - 1 || arr[mid] != arr[mid + 1]) {
return mid;
} else {
low = mid + 1;
}
}
}
return -low - 1; // 找不到则返回插入位置
}
Pythonの実装コード
def searchFirst(arr, target):
low = 0
high = len(arr) - 1
while low <= high:
mid = (low + high) >> 1
midVal = arr[mid]
if midVal > target:
high = mid - 1
elif midVal < target:
low = mid + 1
else:
if mid == 0 or arr[mid] != arr[mid - 1]:
return mid
else:
high = mid - 1
return -low - 1
def searchLast(arr, target):
low = 0
high = len(arr) - 1
while low <= high:
mid = (low + high) >> 1
midVal = arr[mid]
if midVal > target:
high = mid - 1
elif midVal < target:
low = mid + 1
else:
if mid == len(arr) - 1 or arr[mid] != arr[mid + 1]:
return mid
else:
low = mid + 1
return -low - 1
Javaアルゴリズムのソースコード
class Solution {
public int[] searchRange(int[] nums, int target) {
return new int[]{search(nums, target, true), search(nums, target, false)};
}
public int search(int[] nums, int target, boolean isFirst) {
int low = 0;
int high = nums.length - 1;
while(low <= high) {
int mid = (low + high) >> 1;
int midVal = nums[mid];
if(midVal > target) {
high = mid - 1;
} else if(midVal < target) {
low = mid + 1;
} else {
if(isFirst) {
// 查找元素的第一个位置
if(mid == 0 || nums[mid] != nums[mid-1]){
return mid;
} else {
high = mid - 1;
}
} else {
// 查找元素的最后一个位置
if(mid == nums.length - 1 || nums[mid] != nums[mid + 1]) {
return mid;
} else {
low = mid + 1;
}
}
}
}
return -1;
}
}
JavaScriptアルゴリズムのソースコード
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
function search(nums, target, isFirst) {
let low = 0;
let high = nums.length - 1;
while(low <= high) {
const mid = (low + high) >> 1;
const midVal = nums[mid];
if(midVal > target) {
high = mid - 1;
} else if(midVal < target) {
low = mid + 1;
} else {
if(isFirst) {
if(mid == 0 || nums[mid] != nums[mid-1]) {
return mid;
} else {
high = mid - 1;
}
} else {
if(mid == nums.length - 1 || nums[mid] != nums[mid + 1]) {
return mid;
} else {
low = mid + 1;
}
}
}
}
return -1;
}
return [search(nums, target, true), search(nums, target, false)];
};
Pythonアルゴリズムのソースコード
class Solution(object):
def searchRange(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
return self.search(nums, target, True), self.search(nums, target, False)
def search(self, nums, target, isFirst):
low = 0
high = len(nums) - 1
while low <= high:
mid = (low + high) >> 1
midVal = nums[mid]
if midVal > target:
high = mid - 1
elif midVal < target:
low = mid + 1
else:
if isFirst:
if mid == 0 or nums[mid] != nums[mid-1]:
return mid
else:
high = mid - 1
else:
if mid == len(nums) - 1 or nums[mid] != nums[mid + 1]:
return mid
else:
low = mid + 1
return -1