二分查找算法

二分查找

二分查找是一种基础算法,通常条件是待查找序列是有序的,从有序序列中查找便可以不用逐个元素进行比较。所谓二分查找仅是不断比较中间的元素缩小待查找的范围,这样就可是使得查找的平均时间复杂度降至 O ( l o g N ) O({log}N) O(logN)。当然在一般的题目中不会直接让写二分查找,当然他的基础代码还是需要牢记和理解。

int binarySearch(vector<int> nums, target){
    int left = 0, right = nums.size()-1;
    while(left <= right){
        int mid = left+(right-left)/2;
        if(nums[mid] == target)return mid;
        else if(nums[mid] > target)right = mid-1;
        else left = mid+1;
    }
    return -1;
}

题目1: leetcode 34

描述:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]

输入输出示例:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

思路1: 暴力搜索

很直观我们可以直接从左往右搜索最左边第一个等于target的值的索引leftIndex,然后从右往左搜索第一个等于target的值的索引rightIndex,最后返回[leftIndex, rightIndex]。实现也十分简单:

vector<int> searchRange(vector<int>& nums, int target) {
    int leftIndex=-1, rightIndex=-1, n=nums.size();
    for(int i=0; i<n; i++){
        if(nums[i] == target){
            leftIndex = i;
            break;
        }
    }
    for(int i=n-1; i>=0; i--){
        if(nums[i] == target){
            rightIndex = i;
            break;
        }
    }
    return {leftIndex, rightIndex};
}

我们可以看到这里时间复杂度其实是 O ( n ) O(n) O(n),因为他是直接对nums数组做一个循环来实现搜索,而空间复杂度只用到了常数空间,所以为 O ( 1 ) O(1) O(1)

思路2: 二分查找

想到二分查找也是十分容易的,因为题目中给出的nums是有序的,在一个有序的序列中查找当然是要选择复杂度更低的二分查找。但是最原始的二分查找找的是在序列中的索引,这个题目中的要找的是一个等于target数值的一个索引范围,所以直接上二分查找不能得到最终的结果,因此需要做一个小转换,去寻找第一个大于target的下标,具体实现如下:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int leftIndex = binarySearch(nums, target-1);
        int rightIndex = binarySearch(nums, target)-1;
        if(leftIndex<=rightIndex && nums[leftIndex]==nums[rightIndex]){
            return {leftIndex, rightIndex};
        }
        return {-1, -1};
    }
private:
    int binarySearch(vector<int>& nums, int target){
        int left=0, right=nums.size()-1, ans=nums.size();
        while(left <= right){
            int mid = left+(right-left)/2;
            if(nums[mid] > target){
                right = mid-1;
                ans = mid;
            }
            else{
                left = mid+1;
            }
        }
        return ans;
    }
};

时间复杂度: O ( l o g N ) O(logN) O(logN),空间复杂度 O ( 1 ) O(1) O(1)

题目2:leetcode33

题目描述: 整数数组 nums 按升序排列,数组中的值 互不相同 。在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
输入输出示例:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

思路1:找旋转

直观想法,我们只要找到了在哪个地方旋转了,然后再根据targetnums[0]判断要寻找的数在哪一段,最后二分查找。第一步找到什么地方做了旋转,这里同样可以通过二分法,试想从左右两边开始查找,当中间值nums[mid]>nums[right]时,说明被旋转的点在mid右边,所以left=mid+1。当中间值nums[mid]<nums[right]时,说明被旋转点在mid左边,所以right=mid-1。有了被旋转的位置,那就是当target>nums[0]时,target在旋转值的左段,否则在旋转值的右段,然后利用二分查找即可获得要查找的位置。实现一下:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int pivot = searchPivot(nums);
        if(nums[0]>target || pivot==0){
            return binarySearch(nums, target, pivot, nums.size()-1);
        }
        return binarySearch(nums, target, 0, pivot);
    }
private:
    // pivot < nums[right],则最小值在左半边,收缩右边界。
    // pivot > nums[right],则最小值在右半边,收缩左边界。
    int searchPivot(vector<int>& nums){
        int left=0, right=nums.size()-1;
        while(left < right){
            int pivot = (right+left)/2;
            if(nums[pivot] < nums[right]){
                right = pivot;
            }
            else{
                left = pivot+1;
            }
        }
        return left;
    }
    int binarySearch(vector<int> nums, int target, int left, int right){
        while(left <= right){
            int mid = left+(right-left)/2;
            if(target == nums[mid])return mid;
            else if(target > nums[mid])left = mid+1;
            else right = mid-1;
        }
        return -1;
    }
};

思路2:直接二分查找

根据第一种思路中找旋转点的想法,我们可以根据pivot索引的值和left索引的值判断当前的pivot处于哪一段,然后判断targetnums[pivot]的关系来更新leftright

  • nums[pivot] >= nums[left]时。这时如果target>nums[left]并且target<nums[pivot],那说明targetleftpivot之间,索引更新right=pivot-1。否则就说明targte出现在pivot的右边段,更新left=povit+1
  • nums[pivot] < nums[left]时。当target > nums[pivot]并且target <= nums[right],更新left=pivot+。否则说明targetpivot左边段,更新right=pivot-1
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left=0, right=nums.size()-1;
        while(left <= right){
            int pivot = left+(right-left)/2;
            if(nums[pivot] == target)return pivot;
            if(nums[pivot] >= nums[left]){
                if(nums[left]<=target && target<nums[pivot]){
                    right = pivot-1;
                }
                else{
                    left = pivot+1;
                }
            }
            else{
                if(nums[pivot]<target && target<=nums[right]){
                    left = pivot+1;
                }
                else{
                    right = pivot-1;
                }
            }
        }
        return -1;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值