二分查找原理及使用场景

建议使用左闭右开区间[l, r)查找。二分查找的最后,索引l,r会落到右区间第一个元素位置。因此但凡是能够见数组分成左右两个区间的都能应用二分查找法。

1、普通查值

常见问题方式:寻找含重复值的有序数组 `[...,a, tar, tar, tar,.b....]`,找到tar元素的上下边界位置。

(1)寻找下边界:

俗称lower_bound,将[...a]看做左区间,[tar,tar,tar,b...]看做右区间,因此左区间向右移动的条件是nums[mid] < tar,否则右区间向左移动;(如果找不到,右区间第一个大于tar的元素)。

int lower_bound(int[] nums, int n, int tar)
{
    int left = 0, right = n;
    while(left < right)
    {
        int mid = left + (right-left)/2;
        if(nums[mid]<tar) left = mid+1;
        else right = mid;
    }
    return left;//查找结束后,不管找没找到目标值,right==left,返回哪个都行。
}

(2)寻找上边界

俗称upper_bound,将[...a,tar,tar,tar]看做左区间,[b...]看做右区间,upper bound是l-1。因此左区间向右移动的条件是nums[mid] <= tar,否则右区间向左移动;(如果找不到,右区间第一个大于tar的元素)。

2、旋转数组查找

含义:对于[1 2 3 4 5]向右旋转两次为[4 5 1 2 3]称为旋转数组。

(1)无重复元素时 查找指定元素位置

33. 搜索旋转排序数组

思路:对于[1 2 3 4 5],[4 5 1 2 3]两种旋转数组类型,可以根据target与nums[0]大小关系分成左右两区间。当target在左区间(tar>-nums[0])时,nums[mid]在右区间或在左区间但大于target都要向左移动,只有nums[mid]在左区间且小于taget向右移动。当taget在右区间同理分析。

平均时间复杂度O(logn),最坏时间复杂度O(n)

    int search(vector<int>& nums, int target) {
        if (nums.empty()) return -1;
        if (target == nums[0]) return 0;

        int n = nums.size(), l = 0, r = n;
        while (l < r) {
            int mid = (l+r)/2;
            if (nums[mid] == target) {
                return mid;
            }
            if (target >= nums[0]) {//target在左区间
                //当前值在左区间且当前值小于目标值往右走
                if (nums[mid] >= nums[0] && nums[mid] < target)
                    l = mid +1;
                else//其他情况都往左走 
                    r = mid;
            }
            else {
                //当前值在右区间且当前值大于目标值往左走
                if (nums[mid] <= nums[0] && nums[mid] > target) 
                    r = mid;
                else
                    l = mid +1;
            }
        }
        return -1;
    }

(2)含重复元素时 查找元素位置

81. 搜索旋转排序数组2

旋转数组含有重复元素,且重复元素在数组首尾时,就无法根据taget与nums[0]关系分成左右区间了,我们可以左右收缩派出重复元素,再根据上一题思路进行查找。

    bool search(vector<int>& nums, int target) {
        int n = nums.size(), l=0, r=n-1;
        if (target == nums[0]) return true;//因为要跳过首尾重复元素,先判断下

        while (l+1 <n && nums[l] == nums[l+1]) ++l;
        while (r-1>=0 && nums[r] == nums[r-1]) r--;
        if (l >= r) return false;//全部重复
        if (nums[l] == nums[r]) --r;//上面收缩后仍有可能左右边界元素相同
        ++r;//右边界取不到

        int begin = nums[l];
        while (l < r) {
            int mid = (l+r)/2;
            if (target == nums[mid]) {
                return true;
            }
            if (target >= begin) {
                if (nums[mid] >= begin && nums[mid] < target) l = mid+1;
                else r = mid;
            }
            else {
                if (nums[mid] < begin && nums[mid] > target) r = mid;
                else l = mid+1;
            }
        }
        return false;
    }

(3)无重复值 寻找最小值

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

二分法是数组按某种规律分成两个区间就可以用,最终索引落到右区间第一个元素。

该题两种情况:3 4 5 1 2       [3 4 5]向右 [1 2]向左

                1 2 3 4 5       [1 2 3 4 5]向左

所以规律是nums[mid] < nums.back向左,但是和nums[0]比较不是很符合

    int findMin(vector<int>& nums) {
        int n = nums.size(), l = 0, r = n;
        while (l < r) {
            int mid = (l+r)/2;
            if (nums[mid] > nums.back()) 
                l = mid+1;
            else r = mid;
        }
        return nums[l];
    }

(4)有重复值 寻找最小值

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

同理向去处左右重复值.平均时间复杂度O(logn),最坏时间复杂度O(n)

    int findMin(vector<int>& nums) {
        int n = nums.size(), l =0, r = n-1;
        while (l<n-1 && nums[l] == nums[l+1]) 
            ++l;
        while (r >=1 && nums[r] == nums[r-1])
            --r;
        if (l >= r) return nums[0];

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

(5)寻找峰值元素

对于单峰值数组。有三种情况,峰值在中间,递增数组、递减数组。根据趋势将数组分为左右区间

    int findPeakElement(vector<int>& nums) {
        int n = nums.size(), l = 0, r=n;
        while (l < r) {
            int mid = (l+r)/2;
            //把n-1放到右区间,如果数组一直递增,那么l最终落到n-1上符合题目
            if (mid+1<n && nums[mid] < nums[mid+1]) l = mid + 1;
            else r = mid;
        }
        return l;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值