算法入门——二分查找

目录

   1、二分模板

2、习题

1.704.二分查找

 2.35.搜索插入位置

3.744. 寻找比目标字母大的最小字母

 4.69. x 的平方根

5.1351. 统计有序矩阵中的负数 

6.74. 搜索二维矩阵 

7.34. 在排序数组中查找元素的第一个和最后一个位置

8.33. 搜索旋转排序数组

9.153. 寻找旋转排序数组中的最小值 

10.154. 寻找旋转排序数组中的最小值 II

3、总结


   1、二分模板

         二分查找可谓是入门的经典算法了,有固定的模板,但是呢每个人的模板又不太一样,为什么呢?

因为就题论题,主要是边界问题,所以我们需要在做题中,打造一个专属于自己的模板,一看就懂,即使是需要做一点小变化,自己写起来也能得心应手,

下面是我自己的二分模板:

int l = 1, r = n;

while (l < r) {
    int mid = l+(r-l)/2;
    if (mid > target)
        l = mid + 1;//因为mid当前这个值不可能等于target了
                    //所以l更新为mid+1,在[mid+1,r]上查找
    else
        r = mid;//因为mid当前这个值可能等于target了
                //所以r更新为mid,在[l,mid]上查找
}
return r;

 这个模板是在 [1,n] 上查找target,跳出循环时 l==r 所以我们最终返回还是r,都是一样的;

这里的 int mid = l+(r-l)/2 我用的是向下调整,但为什么是这个模式呢?

l+(r-l)/2

=l+r/2-l/2

=l/2+r/2

=(l+r)/2

写成(l+r)可能会爆int,就是超出int范围,有点危险,所以写成l+(r-l)/2,就可以避免这个问题;

 了解了二分模板,我们来做几道题练习一下:

2、习题

题目来源均为LeetCode

1.704.二分查找

注意事项:

标准二分查找,套上面我给的模板,最后加个判断条件,如果当前数不是target,返回-1;

int search(int* nums, int numsSize, int target) {
    int l=0,r=numsSize-1;
    while(l<r)
    {
       int mid = l + (r - l) / 2;
        if(nums[mid]<target)
            l=mid+1;
        else
            r=mid;
    }
    if(nums[r]!=target)
        return -1;
    return r;

}

 2.35.搜索插入位置

注意事项;

如果数组中存在target,直接返回其下标,若不存在,我们需要二次判断,如果target大于当前值,则返回当前值的下标+1,如果target<=当前值,则返回当前值的下标;

int searchInsert(int* nums, int numsSize, int target) {
    int l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (nums[mid] < target)
            l = mid + 1;
        else
            r = mid;
    }
    if (target > nums[r])
        return r + 1;
    return r;
}

3.744. 寻找比目标字母大的最小字母

注意事项:

这个题目数组是按照非递减排序的,就是可能有重复元素,所以我们就要优化一下我们的二分模板,采用下面的二分模板,可以取到重复元素中最右边的值,然后最后再判断一下target和当前值的关系,所以我们要就题论题,,二分法往往不是一次就能做对的,多次调试次才可以;

char nextGreatestLetter(char* letters, int lettersSize, char target) {
    int l = 0, r = lettersSize - 1;
    // target比所有目标值都大,返回letters[0]
    if (target >= letters[r])
        return letters[0];
    // 二分查找
    while (l < r) {
        int mid = l + (r - l + 1) / 2; // 向上取整
        if (letters[mid] > target)
            r = mid - 1;
        else
            l = mid;
    }
    // 如果target大于等于当前值,那么就返回当前值的下一个
    if (target >= letters[r])
        return letters[r + 1];
    // 如果target小于当前值,就返回当前值
    return letters[r];
}

 4.69. x 的平方根

注意事项:

用二分法解决数学问题,还是那个模板,本质是不变的,但是我们要考虑数学问题 

int mySqrt(int x) {
    //如果x==1,直接返回1
    if (x == 1)
        return 1;
    //要开 long long类型,不然会超出范围
    long long l = 1, r = x;
    while (l < r) {
        long long mid = l + (r - l) / 2;
        if (mid * mid < x)
            l = mid + 1;
        else
            r = mid;
    }
    //返回的r要么r*r刚好等于x,直接返回r就好了,要么大于x,所以要返回r-1
    if (r * r == x)
        return r;
    return r - 1;
}

5.1351. 统计有序矩阵中的负数 

 注意事项:

相信做到这里,你已经对二分查找有了不少理解吧,那么这道题可能对你来说不难。难的是格式的掌控,不太明白矩阵(也就是二维数组)的格式书写,当我们掌握了格式,对每一行的数据进行二分查找,就很简单了。

int binarysearch(int** grid, int i, int col) {
    int l = 0, r = col-1;
    while (l < r) {
        int mid = (l + r) / 2;
        if (grid[i][mid] >= 0)
            l = mid + 1;
        else
            r = mid;
    }
    if (grid[i][r] >= 0)
        return 0;//如果没有负数,返回0
    return col - r;//返回负数的个数
}
int countNegatives(int** grid, int gridSize, int* gridColSize) {
    // row是行  col是列
    int row = gridSize, col = *gridColSize;
    int sum = 0;
    for (int i = 0; i < row; i++) {
        sum += binarysearch(grid, i, col);
    }
    return sum;
}

6.74. 搜索二维矩阵 

 注意事项:

按照我的思路,先判断target是否存在矩阵中,如果存在,判断最后一列的每一行,确定target存在哪一行,然后在这一行中找到target,如果找不到,返回false;

bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize,
                  int target) {
    int row = matrixSize;
    int col = *matrixColSize;
    //判断target不属于矩阵
    if (target > matrix[row - 1][col - 1] || target < matrix[0][0])
        return false;
    //判断target是在第r行还是第r+1行
    int l = 0, r = row - 1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (matrix[mid][col - 1] < target)
            l = mid + 1;
        else
            r = mid;
    }
    if (target > matrix[r][col - 1])
    // r+1行查找
    {
        int l1 = 0, r1 = col - 1;
        while (l1 < r1) {
            int mid = l1 + (r1 - l1) / 2;
            if (matrix[r + 1][mid] < target)
                l1 = mid + 1;
            else
                r1 = mid;
        }
        if (matrix[r + 1][l1] == target)
            return true;
    } 
    //在第r+1行查找
    else {
        int l2 = 0, r2 = col - 1;
        while (l2 < r2) {
            int mid = l2 + (r2 - l2) / 2;
            if (matrix[r][mid] < target)
                l2 = mid + 1;
            else
                r2 = mid;
        }
        if (matrix[r][l2] == target)
            return true;
    }
    return false;
}

7.34. 在排序数组中查找元素的第一个和最后一个位置

注意事项:

题上要求用O(logN)的时间复杂度,我们就知道是用二分了,我的思路是先malloc一个数组,并初始化两个元素都为-1,然后查找两个目标值,用两个二分查找,不过两个二分查找的写法有点差别,但当然这两种写法,我们前面做的题都用过了,相信大家画图理解一下也是可以的;

int* searchRange(int* nums, int numsSize, int target, int* returnSize) {
    // 初始化arr
    int* arr = malloc(sizeof(int) * 2);
    *returnSize = 2;
    arr[0] = -1;
    arr[1] = -1;
    // 数组为空的情况
    if (numsSize == 0)
        return arr;
    // 查找目标值的开始位置
    int l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (nums[mid] < target)
            l = mid + 1;
        else
            r = mid;
    }
    if (nums[r] == target) {
        arr[0] = r;
    }
    // 查找目标值的结束位置
    l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = l + (r - l + 1) / 2; // 向上取整,避免死循环
        if (nums[mid] <= target)
            l = mid;
        else
            r = mid - 1;
    }
    if (nums[r] == target) {
        arr[1] = r;
    }

    // 数组中不存在目标值,直接返回arr
    return arr;
}

8.33. 搜索旋转排序数组

注意事项:

一开始我也没有想出来,看的题解,总结一下就是比较复杂的二分(这里的复杂是比较难想,逻辑性比较强,二分的本质是不变的),注释写的比较详细,大家可以结合示例看看,要是不理解的地方可以在评论区留言呀!

int search(int* nums, int numsSize, int target) {
    int l = 0, r = numsSize - 1;
    while (l <= r) {//如果数组中存在目标值,最终l==r,返回mid
        int mid = l + (r - l) / 2;//取中点mid,防止 l+r 爆int
        if (nums[mid] == target)
            return mid;

        //[l,mid]在前半部分
        if (nums[l] <= nums[mid]) {
            if (target >= nums[l] && target < nums[mid])
                r = mid - 1;//如果target在前半部分,更新r的位置
            else
                l = mid + 1;//如果target在后半部分,更新l的位置
        }
        //[mid+1,r]在后半部分
        else {
            if (target > nums[mid] && target <= nums[r])
                l = mid + 1; //如果target在后半部分,更新l的位置
            else
                r = mid - 1;//如果target在前半部分,更新r的位置
        }
    }
    //如果数组中不存在目标值,返回-1
    return -1;
}

9.153. 寻找旋转排序数组中的最小值 

注意事项:

看代码注释!

两种方法:

法一:找到最大值,然后最大值的后面就是最小值

int findMin(int* nums, int numsSize) {
    int l = 0, r = numsSize - 1;
    //假设该数组经过n次旋转还是有序的,返回数组的最小值,也就是数组的首元素
    if(nums[l]<=nums[r])
    return nums[l];
    //注意这个地方是nums[mid]和nums[l]比较,并且mid是向上取整的
    //这种方法是找到数组的最大值,然后再最大值的下标+1,得到最小值
    while (l < r) {
        int mid = (r+l+1) / 2;
        if (nums[mid] > nums[l])
            l = mid;
        else
            r = mid-1;
    }
    return nums[r+1];
}

 法二:直接找最小值

int findMin(int* nums, int numsSize) {
    int l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = (r + l) / 2;
        if (nums[mid] > nums[r])
            l = mid + 1;
        else
            r = mid;
    }
    return nums[r];
}

就是两种方法的边界处置不太一样;

10.154. 寻找旋转排序数组中的最小值 II

注意事项:

这道题和上一道题比,多了重复的元素, 解法就是当nums[mid]==nums[r]时,--r,就可以巧妙的解决这个问题,你问我怎么知道的,多次实践+看题解,题解里面大佬多,就会了;

int findMin(int* nums, int numsSize) {
    int l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (nums[mid] > nums[r])
            l = mid + 1;
        else if (nums[mid] == nums[r])
            --r;
        else
            r = mid;
    }
    return nums[r];
}

3、总结

二分查找经典并不简单,要从实践中总结经验,还是要多练,菜就多练

多多重复,百炼成钢!

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值