二分
该面对的总是要面对
LeetCode34. 在排序数组中查找元素的第一个和最后一个位置
题意是,在某个排序数组里找到target的第一次出现和最后一次出现。
有两种解法
第一种 [l,r]
while(l<=r)
{
int mid = (l+r)/2;
if(nums[mid]<target) l = mid+1;
else r = mid-1;
}
a[0] = l;
if((a[0]==nums.size())||nums[a[0]]!=target) a[0] = -1;
由于 [l,r]
-
合法区间
把问题转换为对合法区间的切分,在寻找左端点的情况下,相当于<target为合法,>=target为不合法。当mid值小于target时,说明此时l到mid都是合法的(包括mid)因此将l右移缩小范围。当mid大于等于target时,说明l到mid不合法,可以舍弃,使r = mid-1(即一个有可能的合法值)。 -
边界问题
二分到最后l=r,(l+r)/2=l=mid,此时- 若合法:l=mid+1,结果就是l成为了不合法的那一个(因为mid也就是r合法)
- 若不合法:r=mid-1,l不变,l依旧不合法。
此时l=r+1。找到的最终合法值是r,在此题中就是第一个target的左边那一个,所以理论上l应该是第一个target。
-
若查找失败
如果停在了数组内部,那么找到的r依旧是比target小一点的那一个,但nums[l]!=target。
如果全部数字都大于target,依旧可以用nums[l]!=target。
如果全部数字小于target此时l应该越界了,判断是否等于n即可。
对右边端点的查找同理,合法区间变为<=target和>target,最终l非法r合法。找到的最大一个target是r,l是比target大一点点那个。
l = 0, r = nums.size()-1;
while(l<=r)
{
int mid = (l+r)/2;
if(nums[mid]<=target) l = mid+1;
else r = mid-1;
}
a[1] = r;
if(l==0||nums[a[1]]!=target) a[1] = -1;
查找失败的情况有所不同,因为最终判断合法是通过判断nums[pos]?=target,所以在这里应该用r来判断,r超出数组范围的情况就是r=-1,l=0。
第二种 [l,r)
在区间选择上,天生l合法r不合法,因此当l=r(冲突)时退出循环。
int l = 0, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] < target) l = mid + 1;
else r = mid;
}
if (r == n || nums[r] != target) return {-1, -1};
-
合法区间
在转移的时候r=mid,而不是mid-1,因为此时mid已经验证不合法,然后r刚好是一个不合法值。感觉就是l代表合法阵营,r代表不合法阵营,在搜索他们那个分界点,更直观? -
边界问题
二分到最后l=r-1,(l+r)/2=l=mid,此时- 若l合法:l+1,最终l不合法了
- 若l不合法:r=l,l不变,l依旧不合法。
此时l=r=第一个target。
-
若查找失败
如果停在了数组内部,nums[l]!=target。
如果全部数字都大于target,依旧可以用nums[l]!=target。
如果全部数字小于target此时l应该越界了,判断是否等于n即可。
实际上可以假装nums[-1]的坐标有一个-inf,nums[n]有一个inf,就不用判断边界辣