当前位置: 首页 > article >正文

二分查找理解

二分查找

示例代码


int binarySearch(int arr[], int l, int r, int x)
{
    if(r >= l) {
        int mid = l + (r - l) / 2; // 计算中间索引

        // 如果元素正好在中间位置
        if (arr[mid] == x)
            return mid;

        // 如果元素大于中间元素,则在右半部分继续查找
        if (arr[mid] < x)
            return binarySearch(arr, mid + 1, r, x);

        // 否则,在左半部分继续查找
        return binarySearch(arr, l, mid - 1, x);
    }

    // 没有找到元素
    return -1;
}

虽然很简单,但是有几个值得一提。
我们不妨认为二分是区间缩小,每次查找少一半的区间,来看以下特性

  • 凡是存在整形截断的编程语言,二分查找,最后必然左端点,右端点汇聚在同一个点。也就是只剩下一个值。 原因也很简单,任意一个大于1的数,不断/2,在有整形截断的情况下,最后必然有结果是1。

有了二分查找,不如进一步思考,如果求这个数所在该数组的最小区间呢?

  • 判出条件自然就是需要改变,R>L
  • 然后就是L取mid+1,或者是R取mid-1的问题了,如何抉择呢?
  • 实际上,L取mid+1,就是不断让L向前移动,R取mid,那么就保证R所在的值是大于于目标值。且由于整型截断,R与L之间即使只相差1,也会在mid计算时总是计算的是整个区间中间值左边的那一个,这样使得必然是L=mid+1,而使得L与R重合(L=R),让对应的值是 区间是[ arr[R-1],arr[R] ]
  • 对应的,你如果取R是mid-1,L=mid,就可能导致死循环,因为整型截断,导致mid=L,而L与R的值相差1时,mid永远时L。

int binarySearch(int arr[], int l, int r, int x)
{
    if(r > l) {
        int mid = l + (r - l) / 2; // 计算中间索引

        // 如果元素正好在中间位置
        if (arr[mid] == x)
            return mid;

        // 如果元素大于中间元素,则在右半部分继续查找
        if (arr[mid] < x)
            return binarySearch(arr, mid , r, x);

        // 否则,在左半部分继续查找
        return binarySearch(arr, l, mid - 1, x);
    }

    // 没有找到元素
    return -1;
}

当你了解了上面的知识,不妨看看下面这个题,思考一下如何用二分操作呢
在排序数组中查找元素的第一个和最后一个位置

答案:


class Solution {
public:
    //这种二分返回的就是第一个小于目标值的下标
    int binaryFind(vector<int>& nums,int target)
    {
        if(nums.empty()) return -1;
        int l = 0,r = (int)(nums.size())-1;
        while(l<r)
        {
            int mid = (l+r)>>1;
            if(nums[mid] >= target)
            {
                r = mid;
            }
            else
            {
                l = mid +1;
            }
        }
        if(nums[l] >= target)
        {
            return l-1;
        }
        return l;
    }
    vector<int> searchRange(vector<int>& nums, int target) {
        int r = binaryFind(nums,target+1), l =  binaryFind(nums,target);
        if(l>=r)
        {
            return {-1,-1};
        }
        return {l+1,r};
    }
};

思考上面代码核心思维,起始就是利用二分时下标的整形截断,使得最终求出来的那个值总是我们求值的最左边一个值,因此再对二分函数稍作处理,将其改为返回其刚好小于那个值的下标,整个函数封装的就很漂亮勒。

寻找两个正序数组的中位数

这题本身二分查找的思路并不难,难得是将其和两个数组正确的下标处理结合起来

题解的核心:
1.假设mid1是nums1的一个数的下标,mid2同理,合理中位数划分是 0-mid1-1,和0-mid2-1这些数字来作为左边半个部分,这样中位数就是左边半个部分的最大值,和右边半部分的最小值,根据奇偶情况算一下而已。
2.假设我们决定了mid1的值,mid2如何计算呢?m = nums1.size,n = nums2.size
根据判断,mid1左边显然有mid1个数字,那么mid2左边就应该:(m+n+1)/2 -mid1
A.我们总是让对应划分数字时,小的那部分, 0-mid1-1,和0-mid2-1后面称之为leftpart(rightpart类似),leftpart应该再偶数: (m+n)/2 - mid1,奇数我们总是把多的数字给leftpart。
B. 有了A的基础,那么m+n是奇数的时候,所以左边要多分一个,所以公式就是(m+n+1)/2-mid1,
我们发现B的公式用于A情况,由于整形截断使不需要做调整的。注意,由于奇数我们给得leftpart多了一个值,那么最后奇数取得就是左边最大值,当然你也可以自行调整。
3.接下来我们看的就是,leftpart的个数解决了,我们要看这次分配是否合理把,就关键看mid-1下标的值是否比mid2的值还要大,如果更大,说明违反了左边值应该都比右边小的原则那么舍去,说明这次取的值太大了,应当缩小mid1,反之就是可能合理的mid1,应当接着扩大mid1,那么根据大小来操作,自然就是对mid1进行二分操作了。
4.我们上面处理的时候发现,mid2可能取超出n对吧,如果nums1太大就会出现这种情况,m=1,n=0情况,那么mid2不就是取1了么?所以我们不要让nums1大于nums2避免这种情况。
5.接着还有一个情况,那就是nums1为空,或者是nums1的值全部比nums2还要小这种情况,因此我们二分nums1,需要能取到m这个值,同时用m这个下标来标识一个最大值,这样好处就是取右边最小值不会有影响,同时我们要取mid1-1的值,那么mid1是0了呢?那么为了不影响左边最大值的取值,就应该使得mid1-1对应的值是INT_MIN。这些道理对nums2是一样的。
6.我们开始从第三步做二分操作,每次注意上面几点即可解决,left总是再变大之后,肯定是更加合适leftpart的,

class Solution {
public:

    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if (nums1.size() > nums2.size())
            nums1.swap(nums2);
        int m = nums1.size(), n = nums2.size();

        int left = 0,
            right =
                m; // 这里right需要取m,有可能第一个数组比第二个数组最小值都小或者空是可能的
        int ans_mid1 = 0, ans_mid2 = 0;

        while (left <= right) {
            int mid1 = (left + right)  >>1;
            int mid2 = (m + n + 1) / 2 - mid1;

            int mid1_1_val = (mid1 ==0 ? INT_MIN:nums1[mid1-1]);
            int mid1_val = (mid1 == m ? INT_MAX : nums1[mid1]);
            int mid2_1_val = (mid2 == 0 ? INT_MIN : nums2[mid2 - 1]);
            int mid2_val = (mid2 == n ? INT_MAX : nums2[mid2]);

            // 接着判断这次值是否可能合理,mid1-1对应的值掯定比mid1的值小,因此直接和mid2比较即可
            if (mid1_1_val <= mid2_val) {
                ans_mid1 = max(mid1_1_val, mid2_1_val);
                ans_mid2 = min(mid1_val, mid2_val);
                left = mid1 + 1;
            } else {
                right = mid1 - 1;
            }
        }
        return (m+n)%2?ans_mid1:(ans_mid1+ans_mid2)/2.0;
    }
};

http://www.kler.cn/a/561341.html

相关文章:

  • xss-lab
  • 计算机视觉算法实战——异常检测(主页有源码)
  • OpenGL 03--顶点着色器、片段着色器、元素缓冲对象
  • 刷题日记5
  • 【TVM教程】为 NVIDIA GPU 自动调度神经网络
  • 【STM32】使用电打火器测试火焰传感器,去掉传感器LED依然亮
  • 使用torch.compile进行CPU优化
  • .NET Core MVC IHttpActionResult 设置Headers
  • IDEA-插件开发踩坑记录-第五坑-没有飞机场导致无法访问GITHUB导致的讨厌问题
  • 【深度学习神经网络学习笔记(一)】深度学习介绍
  • 在vite上使用tailwindcss详细教程(不报错)
  • [C++][cmake]使用C++部署yolov12目标检测的tensorrt模型支持图片视频推理windows测试通过
  • Go语言--语法基础3--下载安装--Linux基础操作命令
  • 图神经网络:拓扑数据分析的新时代
  • DeepSeek AI智能运营:重构企业效率的范式革命
  • kafka数据拉取和发送
  • BUU41 [GYCTF2020]FlaskApp1【SSTI】
  • TSMaster【第十四篇:弹指神通——自动化测试框架】
  • [ Android实战 ] selinux “域继承“的方案(通过属性机制实现)
  • 突破性能极限:DeepSeek开源FlashMLA解码内核技术解析