Java在排序數(shù)組中查找元素的第一個和最后一個位置的方法詳解
一、題目描述
給你一個按照非遞減順序排列的整數(shù)數(shù)組 nums,和一個目標值 target。請你找出給定目標值在數(shù)組中的開始位置和結(jié)束位置。
如果數(shù)組中不存在目標值 target,返回 [-1, -1]。
你必須設(shè)計并實現(xiàn)時間復(fù)雜度為 O(log n) 的算法解決此問題。
示例:
輸入:nums = [5,7,7,8,8,10],target = 8
輸出:[3, 4]
輸入:nums = [5,7,7,8,8,10],target = 6
輸出:[-1, -1]
輸入:nums = [ ],target = 0
輸出:[-1, -1]

二、題解
思路分析:
題目要求我們找到出現(xiàn)target的第一個位置和最后一個位置,首先,我們想到可以通過暴力枚舉的方法來解決該問題,即遍歷數(shù)組,并記錄target第一次出現(xiàn)和最后一次出現(xiàn)的位置。然而,題目要求我們實現(xiàn)時間復(fù)雜度為O(log n)的算法,且題目中給出的數(shù)組為非遞減的數(shù)組,因此,我們可以考慮使用二分查找的方法來解決該問題
由于題目中數(shù)據(jù)量較小,使用遍歷的方法也可以通過該題
遍歷代碼:
class Solution {
public int[] searchRange(int[] nums, int target) {
int first = -1;
int last = -1;
boolean flg = false;//判斷是否是第一個位置
for (int i = 0; i < nums.length; i++) {
//第一個位置
if(nums[i] == target && !flg){
first = i;
flg = true;
}
//最后一個位置
//注意處理特殊情況,即最后一個元素在最后一個位置時,nums[i+1]越界
if(nums[i] == target && (i == nums.length - 1 || nums[i+1] != target)){
last = i;
}
}
int[] ret = {first,last};
return ret;
}
}如何使用二分查找來解決該問題?
題目要求我們找到target在數(shù)組中第一次出現(xiàn)和最后一次出現(xiàn)的位置,利用數(shù)組非遞減的性質(zhì)

首先我們查找元素的第一個位置:
我們可通過target將數(shù)組分為兩部分:

其中,左邊部分為小于target的元素,右邊部分為大于等于target的元素,由于右邊區(qū)域大于等于target,因此右邊區(qū)域最左邊的值即為target第一次出現(xiàn)的位置,即右邊區(qū)域的左端點為所求結(jié)果

我們定義left指向0位置,right指向最后一個元素,mid指向中間位置

若mid指向的元素落在右邊區(qū)間,此時nums[mid]大于等于target,需要更新right的值,由于要找的結(jié)果(target第一次出現(xiàn)的位置)在此區(qū)間內(nèi),即mid所指向的位置可能就是最終結(jié)果,因此不能將right更新為mid - 1,而應(yīng)更新為mid

若mid所指向的元素落在左邊區(qū)間,此時nums[mid]小于target,需要更新 left 的值,由于要找的結(jié)果不在此區(qū)間內(nèi),因此可將left的結(jié)果更新為 mid + 1

當(dāng)left和right之間元素為偶數(shù)個時,此時中間元素有兩個,應(yīng)該選擇哪一個作為中間元素?
由于我們查找的是右邊區(qū)間內(nèi)最左邊的元素,因此,應(yīng)該選擇左邊的元素作為中間元素
若選擇右邊元素作為中間元素,能夠成功查找到結(jié)果嗎?
當(dāng)選擇右邊元素作為中間元素時,此時會出現(xiàn)死循環(huán)的情況,例如:

上圖中,當(dāng)選擇右邊元素作為中間元素時,mid指向的元素落在右邊區(qū)間,此時將right更新為mid,再求mid,此時mid仍為指向剛才位置,即落在右邊區(qū)間,此時再次更新right為mid,再次求mid... 從而死循環(huán)
循環(huán)條件如何設(shè)置?
由于我們將right更新為mid,因此循環(huán)的條件應(yīng)為left < right,若循環(huán)條件設(shè)置為left <= right,當(dāng)left = right時,此時找到結(jié)果,而結(jié)果落在右邊區(qū)間,此時會更新right的值,而right 更新為mid,即當(dāng)前位置,從而死循環(huán)

分析完以上問題后,我們可以嘗試編寫查找右邊區(qū)域最左邊元素的代碼:
//查找target第一次出現(xiàn)的位置(右邊區(qū)間的左端點)
int left = 0,right = nums.length - 1,mid = left + (right - left)/2;
//循環(huán)條件應(yīng)設(shè)置為left < right
//不能設(shè)置為left <= right,否則會死循環(huán)
while(left < right){
//當(dāng)mid所指向的元素落在左邊區(qū)間時,更新left
if(nums[mid] < target){
left = mid + 1;
}else{//當(dāng)mid所指向的元素落在右邊區(qū)間時,此時更新right
//由于右邊區(qū)間的元素大于等于target,即結(jié)果在該區(qū)間內(nèi),
// 因此不能將right更新為mid - 1,而應(yīng)更新為mid
right = mid;
}
//更新mid,當(dāng)有兩個中間元素時,mid應(yīng)指向其中左邊的元素
mid = left + (right - left) / 2;
}此時我們查找target最后一次出現(xiàn)的位置
與查找第一次出現(xiàn)位置的思路相同,我們首先將數(shù)組分為兩個部分:

其中,左邊區(qū)間內(nèi)元素小于等于target,右邊區(qū)間元素大于target
此時,要找的結(jié)果即為左邊區(qū)間的右端點

同樣的,定義left指向0位置,right指向最后一個元素,mid指向中間位置

若mid所指向的元素落在左邊區(qū)間,此時需要更新left的值,由于要找的結(jié)果落在此區(qū)間內(nèi),即mid所指向的位置可能就是最終結(jié)果,因此不能將left更新為mid + 1,而應(yīng)更新為mid

若mid所指向的元素落在右邊區(qū)間,此時需要更新right的值,由于要找的結(jié)果不在右邊區(qū)間,因此可將right的值更新為mid - 1

當(dāng)left和right之間元素為偶數(shù)個時,此時中間元素有兩個,應(yīng)該選擇哪一個作為中間元素?
由于我們查找的是左邊區(qū)間內(nèi)最右邊的元素,因此,應(yīng)該選擇右邊的元素作為中間元素
即 mid = left + (right - left + 1) / 2;
同樣的,當(dāng)選擇左邊元素作為中間元素時,也會造成死循環(huán)

此時left的值一直更新為當(dāng)前位置,造成死循環(huán)
循環(huán)條件的設(shè)置:
循環(huán)條件也同樣應(yīng)該設(shè)置為left < right,否則會死循環(huán)
此時我們嘗試編寫查找左邊區(qū)間右端點代碼:
//查找區(qū)間右端點
left = mid;
right = nums.length - 1;
mid = left + (right - left + 1)/2;
while(left < right){
//當(dāng)mid所指向的值落在右邊區(qū)域時,更新右端點
if(nums[mid] > target){
right = mid - 1;
}else{//當(dāng)mid所指向的值落在左邊區(qū)域時,更新左端點
//由于左邊區(qū)間的元素小于等于target,即結(jié)果在該區(qū)間內(nèi),
//因此不能將left更新為mid + 1,而應(yīng)更新為mid
left = mid;
}
//更新mid的值,若有兩個中間元素時,mid應(yīng)指向其中右邊的元素
mid = left + (right - left + 1) / 2;
}完整代碼:
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] ret = {-1,-1};
//若數(shù)組為空,直接返回ret
if(nums.length == 0){
return ret;
}
//查找target第一次出現(xiàn)的位置(右邊區(qū)間的左端點)
int left = 0,right = nums.length - 1,mid = left + (right - left) / 2;
//循環(huán)條件應(yīng)設(shè)置為left < right
//不能設(shè)置為left <= right,否則會死循環(huán)
while(left < right){
//當(dāng)mid所指向的元素落在左邊區(qū)間時,更新left
if(nums[mid] < target){
left = mid + 1;
}else{//當(dāng)mid所指向的元素落在右邊區(qū)間時,此時更新right
//由于右邊區(qū)間的元素大于等于target,即結(jié)果在該區(qū)間內(nèi),
// 因此不能將right更新為mid - 1,而應(yīng)更新為mid
right = mid;
}
//更新mid,當(dāng)有兩個中間元素時,mid應(yīng)指向其中左邊的元素
mid = left + (right - left) / 2;
}
//此時left = right = mid,使用哪一個變量進行判斷和更新都可以
//若數(shù)組中無值為target的元素,直接返回ret
if(nums[left] == target){
ret[0] = left;
}else{
return ret;
}
//查找區(qū)間右端點
left = mid;
right = nums.length - 1;
mid = left + (right - left + 1)/2;
while(left < right) {
//當(dāng)mid所指向的值落在右邊區(qū)域時,更新右端點
if (nums[mid] > target) {
right = mid - 1;
} else {//當(dāng)mid所指向的值落在左邊區(qū)域時,更新左端點
//由于左邊區(qū)間的元素小于等于target,即結(jié)果在該區(qū)間內(nèi),
//因此不能將left更新為mid + 1,而應(yīng)更新為mid
left = mid;
}
//更新mid的值,若有兩個中間元素時,mid應(yīng)指向其中右邊的元素
mid = left + (right - left + 1) / 2;
}
ret[1] = left;
return ret;
}
}題目來自:
34. 在排序數(shù)組中查找元素的第一個和最后一個位置 - 力扣(LeetCode)
總結(jié)
到此這篇關(guān)于Java在排序數(shù)組中查找元素的第一個和最后一個位置的文章就介紹到這了,更多相關(guān)Java排序數(shù)組中查找元素內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java并發(fā)系列之AbstractQueuedSynchronizer源碼分析(條件隊列)
這篇文章主要為大家詳細介紹了Java并發(fā)系列之AbstractQueuedSynchronizer源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02
Java為什么匿名內(nèi)部類參數(shù)引用需要用final進行修飾?
今天小編就為大家分享一篇關(guān)于Java為什么匿名內(nèi)部類參數(shù)引用需要用final進行修飾?,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-04-04
SpringBoot中MybatisX插件的簡單使用教程(圖文)
MybatisX 是一款基于 IDEA 的快速開發(fā)插件,方便在使用mybatis以及mybatis-plus開始時簡化繁瑣的重復(fù)操作,本文主要介紹了SpringBoot中MybatisX插件的簡單使用教程,感興趣的可以了解一下2023-06-06

