day_18 —— LeetCode 704:二分查找

Day_18 —— L e e t C o d e 704 LeetCode 704 LeetCode704:二分查找

题目
在这里插入图片描述

二分查找是一种运行时间复杂度为O(log n)的快速搜索算法。这种搜索算法基于分而治之的原理,为了使该算法正常工作,查找的对象应该是有序的。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            else if (nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        return -1;
    }
};

早在两千多年前,庄子就搞清楚了二分法的精髓,他说:一尺之棰,日取其半,万世不竭

二分法最常见的问题有两个,一个是二分的区间边界不清楚,另一个是二分查找的结果不明确

我们先说第一个问题——边界

早在小学我们就学过,用l表示区间左边界,r表示区间右边界,mid=(l + r) / 2表示二分的中间点。这个在数学里非常明确,但在编程的时候,有一个隐藏的问题被忽略了。究竟这个区间是闭区间呢,还是开区间呢,还是半开半闭区间或者是半闭半开区间?如果这个问题不想清楚,想要一次性写出没有bug的代码,老实说很不容易。

首先,二分终止的条件究竟怎么写,是while (l < r) 还是 while (l <= r) 还是别的?还有,在搜索的时候,我们究竟要不要将a[mid] == v的情况单独判断?我们是判断a[mid] < v还是a[mid] <= v?假设我们选择用a[mid] <= v,得到的结果为true。我们知道答案应该在区间的右半边,我们需要舍弃左边的区间。应该对l赋值,但是我们是赋值成l = m呢还是l=m + 1呢?又是为什么呢?

你看,如果l和r表示的区间不考虑清楚,我们在实际写代码的时候就会遇到这样棘手的问题。坑爹的是,当我们为这些边界头疼的时候,我们并不能意识到这是因为我们没有搞清楚表示区间的方法导致的。往往会觉得是自己不够熟悉。

理论上来说,不论选什么样的区间,只要代码得当,都是可以的,可以说是完全看个人喜好。不过我个人推荐左闭右开,原因很简单,这个和编程当中的数组定义的情况一致。我们都知道,在代码的世界里,数组是从0开始的,一个长度为10的数组,最后一个元素的下标是9。如果使用左闭右开区间,我们将l=0,r=数组长度,就完成了初始化,如果用闭区间,r=长度-1,不免显得有些多余。

假设我们确定了使用左闭右开区间,我们再来看前面说的两个问题。

区间确定了,终止条件也就明确了,左闭右开区间[l, r)不为空的话,r 至少大于等于l + 1。我们要在区间长度大于1的时候执行二分,所以二分的循环条件应该是while (l + 1 < r)。

那么while里的判断条件呢?

我们列举一下,a[mid] 和v的大小关系无非只有三种。

第一种a[mid] = v,很简单,mid就是我们要查找的结果,直接返回。

第二种a[mid] < v,说明我们应该取右边的区间,由于l的位置可以取到,而mid已经不是答案了,所以l = mid + 1。

第三种a[mid] > v,应该取左边的区间,mid不是答案,但是由于r指向的位置本身就不在候选区间里,所以r = mid,而不是mid-1,因为mid-1可能是答案,而r处的位置是取不到的。

到这里,似乎一切完美,我们可以很顺利地写出代码了。但是还没有结束,依然还有一个小问题。

前文说了,a[mid]和v的关系有三种,当a[mid] = v的时候,我们就找到了答案。从这个角度来看,我们二分的时候,通过l和r缩小区间的范围,通过mid来寻找答案。但是既然我们已经折半区间的大小了,那么当区间长度为1的时候,剩下的就是答案,我们为什么还需要通过mid去查找答案呢?如果我们就想通过区间本身来查找答案,那么应该怎么办呢?

也不难,我们需要把a[mid]小于和等于v的两种情况合并,由于a[mid]可能等于v,所以我们不能跳过mid这个位置,l = mid + 1 应该写成l = mid,于是整个代码也就出来了:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        l, r = 0, len(nums)  
        while l + 1 < r:  
            m = (l + r) // 2  
            if nums[m] <= target:  
                l = m  
            else:  
                r = m  
        if nums[l] == target:
            return l
        else:
            return -1

完整二分法线下实现:

#include <stdio.h>
#define MAX 20

// array of items on which linear search will be conducted. 
int intArray[MAX] = {1,2,3,4,6,7,9,11,12,14,15,16,17,19,33,34,43,45,55,66};

void printline(int count) {
  int i;

  for(i = 0;i <count-1;i++) {
    printf("=");
  }

  printf("=\n");
}

int find(int data) {
  int lowerBound = 0;
  int upperBound = MAX -1;
  int midPoint = -1;
  int comparisons = 0;      
  int index = -1;

  while(lowerBound <= upperBound) {
    printf("Comparison %d\n" , (comparisons +1) );
    printf("lowerBound : %d, intArray[%d] = %d\n",lowerBound,lowerBound,
        intArray[lowerBound]);
    printf("upperBound : %d, intArray[%d] = %d\n",upperBound,upperBound,
        intArray[upperBound]);
    comparisons++;
  
    // compute the mid point
    // midPoint = (lowerBound + upperBound) / 2;
    midPoint = lowerBound + (upperBound - lowerBound) / 2;	
  
    // data found
    if(intArray[midPoint] == data) {
        index = midPoint;
        break;
    } else {
        // if data is larger 
        if(intArray[midPoint] < data) {
          // data is in upper half
          lowerBound = midPoint + 1;
        }
        // data is smaller 
        else {
          // data is in lower half 
          upperBound = midPoint -1;
        }
    }               
  }
  printf("Total comparisons made: %d" , comparisons);
  return index;
}

void display() {
  int i;
  printf("[");

  // navigate through all items 
  for(i = 0;i<MAX;i++) {
    printf("%d ",intArray[i]);
  }

  printf("]\n");
}

int main() {
  printf("Input Array: ");
  display();
  printline(50);

  //find location of 1
  int location = find(55);

  // if element was found 
  if(location != -1)
    printf("\nElement found at location: %d" ,(location+1));
  else
    printf("\nElement not found.");
  return 0;
}

在这里插入图片描述
参考内容:

  1. https://www.tutorialspoint.com/data_structures_algorithms/binary_search_algorithm.htm
  2. https://mp.weixin.qq.com/s/upHycaX9ktBbrRIZW3bqcA
  3. https://mp.weixin.qq.com/s/Rle4ZlJaqeOYRxo_xVmX1Q
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值