STL里lower_bound和upper_bound都是找元素的插入位置,区别在于如果插入的值在数组中已经存在,这个插入位置有2种选择,可以插到第一个位置,也可以插到最后一个相同元素后面的位置,也就是插到头部还是尾部的区别。lower_bound是插到头部,upper_bound是append到后面。如果数组里没有和插入值相同的元素,lower_bound和upper_bound的结果是一样的。
给定一个排序数组和一个数target,找出target在数组中第一次出现的位置,如不存在返回-1
写法一:
int binarySearch(vector<int> &array, int target) {
int l = 0, r = array.size() - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (array[mid] < target) l = mid+1;
else r = mid-1;
}
return l < array.size() && array[l] == target ? l : -1;
}
关键点:
1)循环条件 l <= r,和标准二分一样。
2) target == array[mid] 的情况也是 r = mid - 1,没有r=mid 或 l = mid 的分支,保证循环一定可以退出。
3)此算法思路是当target == array[mid]时候区间右端也排除mid,当mid左边仍然有等于target的元素时,第一个等于target的位置仍在区间里;当第一个等于target的位置(即所求)也被排除,问题变为在[l, mid - 1]里找target的插入位置,显然是在末尾之后,即mid。
4)为什么最后是a[l]而不是a[r]? 因为当只剩一个元素l==r的时候, 如果 target比之大,插入位置就是这个位置后面一个位置,即执行了l = mid + 1之后的l;如果target 比之小,那么target应该取代当前元素的位置,还是l
总结:本质上是找lower_bound插入位置,最后的判断:如果插入位置在数组范围,且等于插入位置,则存在元素。
写法二:
int binarySearch(vector<int> &array, int target) {
int l = 0, r = array.size() - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (target <= array[mid]) r = mid;
else l = mid + 1;
}
return array[l] == target ? l : -1;
}
2) invariant property: 如果target在数组里,则一定在区间[l, r]里,当target == array[mid]时,新区间的右端要包括mid,即r = mid。注意这里利用了这一情况:当区间只有2个元素的时候mid指向的是左边的元素l,即不会是r,故r = mid 不会造成死循环
3)循环退出时候,要么l>r, 是一个空区间,说明不存在target;要么l == r, 是一个只有一个元素的区间,要么这个元素就是target,要么不存在target.
写法三:
int binarySearch(vector<int> &array, int target) {
int l = 0, r = array.size() - 1;
while (l < r - 1) {
int mid = l + (r - l) / 2;
if (target <= array[mid]) r = mid;
else l = mid + 1;
}
if (array[l] == target) return l;
if (array[r] == target) return r;
return -1;
}
1)循环条件 l < r - 1,也就是循环执行的条件是数组至少有三个元素,这样的好处是mid 不会是l或者r中任意一个,不论是令l = mid 或者 r = mid都不会死循环。
2)invariant property: 如果数组里有target, 则一定在区间[l, r]里。
3)循环退出时候,要么l和r相邻要么l == r,先判断array[l]是不是target, 如果是l就是所求;否则再判断array[r]是不是target, 如果是返回r; 如果以上两种情况都不是,说明target不存在,返回-1。