前言
一、二分查找是什么?
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。
二、二分查找题目
1.引入第一个例子
这道题的代码如下(示例):
int search(int* nums, int numsSize, int target) {
int left = 0; int right = numsSize - 1;//左右两边的下标
while (left <= right) { //如果写成while(left<right)则后面需要判断left=right的情况
int mid = left + (right - left) / 2;//mid也可以写成left+((right-left)>>1);
if (nums[mid] > target) { //中间数大于找的值说明在左边,这题数组是有序的
right = mid - 1;
}
else if (nums[mid] < target) { //中间数小于找的值说明在左边
left = mid + 1;
}
else { //中间数等于找的值
return mid;
}
}
return -1;//整个while里都没找到则返回-1,说明不存在
}
如果不限制参数可以用递归方法
int search(int* nums, int left, int right, int target) {
while (left <= right) {
int mid = left + (right - left )/ 2;
if (nums[mid] > target) {
return search(nums,left,mid-1,target);
}
else if (nums[mid] < target) {
return search(nums, mid + 1, right, target);
}
else {
return mid;
}
}
return -1;
}
int main() {
int arr[] = { 0,3,5,7,9,12 };
int length = sizeof(arr) / sizeof(arr[0]);
int target = 9;
int rs = search(arr,0,length-1,target);
if (rs != -1) {
printf("%d出现在nums中并且下标为%d", target, rs);
}
else {
printf("%d不存在nums中因此返回-1", target);
}
return 0;
}
二分查找要求数列本身有序,所以在选择的时候需要确认数列是否本身有序,如果无序,则还需要进行排序,为什么用二分查找?举个例子查字典我们都会先从中间找起,这样效率最高,我们只需判断要找的字是在前面还是后面,那找要找的数也是这个道理。
2.小试牛刀(查找插入位置)
int searchInsert(int* nums, int numsSize, int target) {
int left = 0; int right = numsSize - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
}
else if (nums[mid] < target) {
left = mid + 1;
}
else {
return mid;
}
}
if (nums[0] > target) {
return 0;
}
else if (nums[numsSize - 1] < target) {
return numsSize;
}
else {
for (int i = 0; i < numsSize; i++) {
if (nums[i]<target && nums[i + 1]>target) {
return i + 1;
}
}
}
return 0;
}
这是我的写法,看了leetcode上官方及其他人的思路,我这种写法非常的挫,我看了他们
的解法觉得很妙但不完全讲的清楚,大家可以自己去看看->leetcode查找插入位置题目
3.试试水平(出现一次的数)
拿到这个题,如果看了我第一篇博客 或者做过寻找落单的数,消失的数等类似题目,应该会想到异或的方法,但这道题最后进阶要求时间复杂度为O(logn),显然异或时间复杂度不符合,这种题乍一看挺没思路的,对于我这样的新手很不友好。那我们看到logn得先想到的二分查找,如果不行那另寻他法。
思考:落单的数有什么特点?
发现这题和下标的奇偶有关,且要找的答案下标一定为奇数,因为其他数是成双出现的。成双出现意味着没有出现落单数时第一个为奇,第二个为偶,出现落单的数会改变奇偶,则第一个为偶第二个为奇数。
int singleNonDuplicate(int* nums, int numsSize) {
int left = 0; int right = numsSize - 1;
while (left < right) {
int mid = left + (right - left)/ 2;
if (mid%2 == 0) { //偶数情况
if (nums[mid] == nums[mid + 1]) { //和下一个数比较判断落单数在左还是右
left = mid + 1; //相等说明在右边
}
else {
right = mid;
}
}
else { //奇数情况
if (nums[mid] == nums[mid - 1]) { //和前面比较 相等说明在右边
left = mid + 1;
}
else { //不相等说明在左边
right = mid-1;
}
}
}
return nums[left];
}
int main(){
int arr[] = { 1,1,2,3,3,4,4,8,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
int rs = singleNonDuplicate(arr, sz);
printf("%d", rs);
return 0;
}
着重说一下偶数情况的else情况,偶数有可能是要找的答案,所以还要分情况考虑因为可能和前面后面都不相等,那就不用二分下去返回答案,这里我一开始写了个嵌套if语句,且while(left<=rigth)是这样,不过在力扣上报了无返回值错,所以想了其他办法,改了while(left<right),当left=right返回出去再改else这里为right=mid-1;发现是错的,因为mid就是结果的话减一就会错过,所以我们不减一,这样去二分,最后当left=right就是要找的落单数。
我能力有限暂且就用了这种,力扣上有很多更优的解法大家可以去看看->leetcode查找出现一次的数。
总结
初识二分查找就到这里了,二分查找的题目有很多,我还会继续去识。本次文章有什么错误和不足之处及时提出哈,谢谢大家!