二分查找经典题目

2024-09-24 19:15:12 浏览数 (1)

二分查找

什么时候能用二分查找?

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

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

朴素的二分查找:

1、循环条件?

left <= right

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

朴素版本二分查找模板
代码语言:javascript复制
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)

代码语言:javascript复制
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)

代码语言:javascript复制
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};
    }
};

求左端点模板

代码语言:javascript复制
       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;

求右端点模板

代码语言:javascript复制
        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

代码语言:javascript复制
// 求左端点的情况
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 左端点的解法

代码语言:javascript复制
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])

代码语言:javascript复制
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来作为参照

代码语言:javascript复制
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

代码语言:javascript复制
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;
    }
};

0 人点赞