二分查找经典题目

目录

二分查找

什么时候能用二分查找?

朴素的二分查找:

朴素版本二分查找模板

704. 二分查找

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

求左端点模板

求右端点模板

69. x 的平方根

35. 搜索插入位置

852. 山脉数组的峰顶索引

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

LCR 173. 点名


二分查找

什么时候能用二分查找?

当数据具有“二段性”的时候,

也就是根据某个规律,能将数据分为两个部分,又根据某个规律可以将其中一部分给舍弃

朴素的二分查找:

1、循环条件?

            left <= right

只有left > right时才能跳出循环,因为每次分割出来的区间的数是未知的,当left == right的时候仍然要检查

朴素版本二分查找模板

while (l <= r)
{
    // 有溢出风险,如果l和r的和 > int,会溢出
    // mid = (l + r) / 2;

    // 以下两个计算mid的方法是防止溢出的
    // 如果数据量为偶数时,更偏向于找左边的数的下标
    mid = l + (r - l) / 2;
    // 让l走[l, r]的一半

    // 如果数据量为偶数时,更偏向于找左边的数的下标
    mid = l + (r - l + 1) / 2;

    if (……)
    {
        r = mid - 1;
    }
    else if (……)
    {
        l = mid + 1;
    }
    else
        return ……;

}

省略号部分是根据不同情况下的二段性来填写的

704. 二分查找

704. 二分查找 - 力扣(LeetCode)

class Solution 
{
public:
    int search(vector<int>& nums, int target) 
    {
        int l = 0, r = nums.size() - 1, mid;
        while (l <= r)
        {
            // 有溢出风险,如果l和r的和 > int,会溢出
            // mid = (l + r) / 2;

            // 以下两个计算mid的方法是防止溢出的
            // 如果数据量为偶数时,更偏向于找左边的数的下标
            mid = l + (r - l) / 2;
            // 让l走[l, r]的一半

            // 如果数据量为偶数时,更偏向于找左边的数的下标
            mid = l + (r - l + 1) / 2;


            if (nums[mid] > target)
            {
                r = mid - 1;
            }
            else if (nums[mid] < target)
            {
                l = mid + 1;
            }
            else
                return mid;
        }
        return -1;
    }
};

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

题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

class Solution 
{
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        if (nums.size() == 0)
            return {-1, -1};

        int ansl = 0, ansr = 0;    

        // 求左端点
        int l = 0, r = nums.size() - 1, mid;
        while (l </**/ r)
        {/*
            1、=的情况可以直接判断,无需继续循环
            2、容易死循环,
            因为求左端点时当target >= mid,则right = mid
            因为求右端点时当target <= mid,则left = mid
        */
            mid = l + (r - l/*求偏左边的点*/) / 2;
            if (nums[mid] < target)
            {
                l = mid + 1;
            }
            else if (nums[mid] >=/**/ target)
            {
                r = mid;// 因为可能mid这个点就是左端点
            }
        }
        if (nums[l] != target)
            return {-1, -1};
        else
            ansl = l;

         // 求右端点
        l = 0, r = nums.size() - 1;
        while (l </**/ r)
        {/*
            1、=的情况可以直接判断,无需继续循环
            2、容易死循环,
            因为求左端点时当target >= mid,则right = mid
            因为求右端点时当target <= mid,则left = mid
        */
            mid = l + (r - l + 1/*求偏右边的那个点*/) / 2;
            if (nums[mid] <=/**/ target)
            {
                l = mid;// 因为可能mid这个点就是右端点
            }
            else if (nums[mid] > target)
            {
                r = mid - 1;
            }
        }
        if (nums[l] != target)
            return {-1, -1};
        else
            ansr = l;

        return {ansl, ansr};
    }
};

求左端点模板

       while (l </**/ r)
       {/*
            1、=的情况可以直接判断,无需继续循环
            2、容易死循环,
            因为求左端点时当target >= mid,则right = mid
            因为求右端点时当target <= mid,则left = mid
        */

            mid = l + (r - l/*求偏左边的点,否则可能死循环,因为l一直更新为 = mid*/) / 2;

            if (nums[mid] < target)// (……)括号里面填写“二段性”的相关判断条件
            {
                l = mid + 1;
            }
            else if (nums[mid] >=/**/ target)// (……)括号里面填写“二段性”的相关判断条件
            {
                r = mid;// 因为可能mid这个点就是左端点
            }
        }
        if (nums[l] != target)
            return {-1, -1};
        else
            ansl = l;

求右端点模板

        while (l </**/ r)
        {/*
            1、=的情况可以直接判断,无需继续循环
            2、容易死循环,
            因为求左端点时当target >= mid,则right = mid
            因为求右端点时当target <= mid,则left = mid
        */

            mid = l + (r - l + 1/*求偏右边的那个点,否则可能死循环,因为l一直更新为 = mid*/) / 2;

            if (nums[mid] <=/**/ target)// (……)括号里面填写“二段性”的相关判断条件
            {
                l = mid;// 因为可能mid这个点就是右端点
            }
            else if (nums[mid] > target)// (……)括号里面填写“二段性”的相关判断条件
            {
                r = mid - 1;
            }
        }

69. x 的平方根

69. x 的平方根 - 力扣(LeetCode)

二段性:
1、平方 > x
2、平方 <= x 

// 求左端点的情况
class Solution 
{
public:
    int mySqrt(int x) 
    {
        if (x < 1)
            return 0;// 边界情况
        int l = 1, r = x;
        long long mid;// 因为x <= 2^31 - 1, mid * mid可能溢出
        while (l < r)
        {
            mid = l + (r - l + 1) / 2;
            if (mid * mid <= x)
                l = mid;
            else// if (mid * mid > x)
                r = mid - 1;// 有 - 1 -> mid计算时要+ 1
        }
        
        return l;
    }
};

35. 搜索插入位置

题目链接:. - 力扣(LeetCode)

该题关键点:找第一个大于target的位置 -------> 二段性:(1) < target (2) >= target
左端点的解法

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

        if (nums[l] < target)
            return l + 1;
        return l;
    }
};

852. 山脉数组的峰顶索引

题目链接:. - 力扣(LeetCode)

 总体先递增再递减 ----> 二段性:左半部分(arr[mid - 1] <= arr[mid]),右半部分(arr[mid - 1] > arr[mid])

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) 
    {
        int l = 0, r = arr.size() - 1, mid;
        while (l < r)
        {
            mid = l + (r - l + 1) / 2;
            if (mid > 0)
            {
                if (arr[mid - 1] <= arr[mid])
                    l = mid;// 因为mid可能就是答案
                else
                    r = mid - 1;// 有 - 1 -----> 上面要 + 1
            }
            else
                break;
        }

        return r;
        // return l;// 都可以
    }
};

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

题目链接:153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

特点:递增(设为AB段)、递增(设为CD段)
二段性:
AB段:nums[i] > nums[n - 1]
CD段:nums[i] <= nums[n - 1]

该解法用的是D来作为参照

class Solution {
public:
    int findMin(vector<int>& nums) 
    {
        int size = nums.size();
        int l = 0, r = size - 1, mid;
        while (l < r)
        {
            mid = l + (r - l) / 2;
            if (nums[mid] > nums[size - 1])// AB段
                l = mid + 1;
            else
                r = mid;
        }

        return nums[r];// 注意要返回的是 最小   元素   ,不是元素  下标
    }
};

LCR 173. 点名

题目链接:LCR 173. 点名 - 力扣(LeetCode)

O(n):1、哈希表 2、直接遍历 3、位运算(和正确数组位运算) 4、求和(用等差数列求正确的和,再减去题目所给元素和)
O(logN):二分查找
              二段性:
              在答案位置的前面,正确的学号 - 数组中的学号 = 0
              在答案位置的后面,正确的学号 - 数组中的学号 = 1
 

class Solution {
public:
    int takeAttendance(vector<int>& records) 
    {
        int l = 0, r = records.size() - 1, mid;
        while (l < r)
        {
            mid = l + (r - l) / 2;
            if (records[mid] - mid == 0)
                l = mid + 1;
            else// if (records[mid] - mid == 1)
                r = mid;
        }

        return records[l] == l ? l + 1 : l;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脑子不好的小菜鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值