34.ソートされた配列内の要素の最初と最後の位置を見つける
昇順で配置された整数配列numsと、ターゲット値targetが与えられます。配列内の指定されたターゲット値の開始位置と終了位置を見つけます。
ターゲット値targetが配列に存在しない場合は、[-1、-1]を返します。
上級:
この問題を解決するためにO(log n)アルゴリズムを設計および実装できますか?
例1:
入力:nums = [5,7,7,8,8,10]、target = 8
出力:[3,4]
例2:
入力:nums = [5,7,7,8,8,10]、target = 6
出力:[-1、-1]
例3:
入力:nums = []、target = 0
出力:[-1、-1]
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
方法1:ブルートフォース検索
いわゆるブルートフォースは、実際には、最初のターゲットを見つけてその添え字を保存し、ターゲットに等しい最後の数が見つかるまで再度検索してから、その添え字を再度格納した後、配列内で1つずつターゲットを探します。
int* searchRange(int* nums, int numsSize, int target, int* returnSize){
int *a=(int*)malloc(sizeof(int)*2);//申请空间
*returnSize = 2;
memset(a,-1,sizeof(a));//先初始化为-1以便后续操作
if(numsSize==1)//由后面可知我是先估计了第i个的下一个的情况,所以至少数组长度为2,所以为长度为1要先单独考虑
{
if(target!=nums[0])
{
return a;
}
else
{
a[0]=0;
a[1]=0;
return a;
}
}
int k = 0;//作为衡量数组中是否出现过与target相等的数
for(int i=0;i<numsSize;i++)
{
if(nums[i]==target)
{
if(k==0)//此为证明是第一次遇见target
{
a[0]=i;
k++;//k变了
}
if(i!=numsSize-1)//下面的条件都是在下一个的情况考虑的,所以要先保证存在“下一个”
{
if(nums[i+1]==target)
{
continue;//即判断他是否为最后一个与target相等的数是靠他之后的数是否为target
}
if(nums[i+1]!=target)
{
a[1]=i;
break;//找到最后一个立马跳出去
}
}
}
}
if(a[1]==-1&&k!=0)//此为找到target但是数组最后一个数也为target的情况
a[1]=numsSize-1;//这样的话在for循环中不会为a[1]赋值,因为他进不去第二个if了
if(k==0)//表示没有找到target的情况
{
a[0]=-1;
a[1]=-1;
return a;
}
return a;
}
しかし、私が予想していなかったのは、暴力的な慣行が非常に効率的だったということでした。
方法2:二分探索
昇順の配列であると言ったので、バイナリ機能を使用して検索を高速化できることが質問からわかります。
しかし、それは通常の二分法とは異なります。ここで見つけることができるものは2つあり、1つは左側の境界で、もう1つは右側の境界です。最初に左側の境界を見つけます。nums[mid]がターゲットよりも大きいか小さい場合は、2点で直接移動できます。ポイントは、そのときNUMS [中期] ==ターゲットが表示され、我々は左に検索する必要があるので、この点は、左の境界ではない可能性があるためで、私たちのループ終了条件がNUMS [中期] ==ターゲットを表示させないので、しかし、一定の左と右を減らして、実際の左の境界が両方で囲まれるようにします。最後に、左とターゲットに対応する値の添え字はすべて1つの位置にあります。このとき、左>右、ジャンプが発生します。ループから出て、左を左の境界に割り当てます。それだけです。右の境界も同じように扱うことができます。
つまり、左の境界をつかんで左を検索すると、右の境界を右に検索できます。
例を挙げて理解することができます。
探している左側の境界が、先頭のnums配列のmidに対応する添え字である場合、つまり、実際には先頭に左側の境界が見つかりました。しかし、わからないので、もう一度左に検索しますが、現時点では、right = mid-1であるため、つまり、left <rightの場合、ターゲット以外を見つけることができないため、一定のループの後、そして最後にleft = rightですが、まだ見つからないため、現時点ではleft = mid + 1で、ループから外れています。この時点でmid = right = leftなので、このmid +1はright + 1であり、これは最初に見つけたmidです。
そして、私が与えたこの例は避けられない状況です。つまり、あなたは左の境界を探しています、そして結局、私が与えた例の場合に来るはずです、つまり、midに対応する添え字は正確に左の境界。
どうして?
そうでない場合は、二分探索によって間隔を狭め続け、最終的にこの状況に到達するためです。
したがって、コードは次のとおりです。
int erfen(int*nums,int numsSize,int target,int index){
int left=0;
int right=numsSize-1;
while(right>=left)//一定要为等于
{
int mid=(left+right)/2;//mid要放里面,因为每次循环mid都要改一次
if(nums[mid]>target)
{
right=mid-1;
}
if(nums[mid]<target)//二分老规矩
{
left=mid+1;
}
if(nums[mid]==target)//当等于时不能直接赋予,因为可能真正的边界在左边或右边
{
if(index==0)//此为找左边界时,此点可能不是左边界,所以不妨向左再找找
{
right=mid-1;
}
else
{
left=mid+1;//同上,此为找右边界
}
}
}
if(index==0)//注意此时我们是默认一定能找到的情况,至于真的找不找的到由后面选择即可
return left;
else
return right;
}
int* searchRange(int* nums, int numsSize, int target, int* returnSize){
int *res=(int*)malloc(sizeof(int)*2);
*returnSize = 2;
res[0]=erfen(nums,numsSize,target,0);//左边界查找
res[1]=erfen(nums,numsSize,target,1);//右边界查找
if (!(res[0] <= res[1] && nums[res[0]] == target && nums[res[1]] == target))
//正是因为原先二分查找是默认nums数组里一定有target的情况,所以我们要进行一次选择
{
res[0] = res[1] = -1;
}
return res;
}