力扣刷题 -- 二分查找

二分查找的定义

二分查找也叫折半查找,比如在一个有序的数组里面找到目标值,它是一种查询效率比较高的算法,时间复杂度 O ( l o g 2 n ) O({log}_2 n) O(log2n)
它的基本思想:将目标值与数组中间的元素进行比较,如果目标值小于中间元素,则在数组的左半部分继续查找,否则在右半部分查找,不断缩小搜索范围,直到找到目标值或确定目标值不存在为止。

lower_bound() 方法 用于在一个 有序 数组 n u m s nums nums 中找到第一个 大于或等于 target 的元素的位置。
(1) 在 n u m s nums nums 内,定义左侧和右侧指针 left 和 right,即 left=0, right=nums.size()-1,表示待查找的范围 [left, right];
(2) 根据 left 和 right 找到中间指针 mid,将 nums[mid] 与 target 进行比较,根据比较结果更新 left 或 right 指针:① 若 nums[mid] < target,相当于 [left, mid] 这个区间的元素都是小于 target,因此 target 可能在 [mid + 1, right] 这个范围内,更新 left = mid+1;② 若 nums[mid] >= target,相当于 [mid, right] 这个区间的元素都是大于等于 target 的,因此第一次出现的 target 可能在 [left, mid - 1] 这个范围内,更新 right = mid-1
(3) 更新结束后,比较 left 和 right:若是待查找的范围还能保持 [left, right],即 left <= right,就继续重复步骤 (2);若是不能保持,比如 right < left,说明在 n u m s nums nums 中找到第一个与 target 相等的元素,或者找到了第一个大于 target 的元素,返回对应的下标,即 return left

lower_bound() 方法的总结:不断将区间 [left, right] 里的中间元素 nums[mid] 与 target 进行比较,根据比较结果不断缩减 [left, right] 区间,直到 left 和 right 不能构成一个区间,就返回对应元素的下标。

① lower_bound() 方法中 left 和 right 满足这个 循环不变量 n u m s [ l e f t − 1 ] < t a r g e t ; n u m s [ r i g h t + 1 ] > = t a r g e t nums[left - 1] < target; nums[right + 1] >= target nums[left1]<target;nums[right+1]>=target,相当于 left 的左边恒小于 target,right 的右边恒大于等于 target,因此 left 最终会停在第一个大于或等于 target 的元素位置,而 right 就会停在的左侧。
② 闭区间的含义:因为 left 和 right 满足这个循环不变量,相当于 [0, left-1] 的元素均小于 target,而 [right+1, nums.size()-1] 的元素是大于等于 target,因此查找范围就落在 [left, right] 这个区间里。

lower_bound2() 方法 与 lower_bound() 方法的作用一致,不同点:
(1) 待查找的范围变为 [left, right),说明该区间中至少包含一个元素,即 left=0, right=nums.size(),因此循环条件为 left < right
(2) 循环不变量变为 n u m s [ l e f t − 1 ] < t a r g e t ; n u m s [ r i g h t ] > = t a r g e t nums[left - 1] < target; nums[right] >= target nums[left1]<target;nums[right]>=target,相当于 left 的左边恒小于 target,right 及其右侧的元素恒大于等于 target;
(3) 根据 mid 指针更新 right 值,若是 nums[mid] >= target,则 right 指针更新,即 right=mid
(4) 最后返回第一个大于或等于 target 的下标,可以返回 left 指针,也可以返回 right 指针;最后 left 和 right 会停在第一个大于或等于 target 的元素位置。

// 左闭右闭区间:待查找的区间 [left, right] 
int lower_bound(vector<int> nums, int target) {
	// 循环不变量:nums[left - 1] < target; nums[right + 1] >= target 
	
	int left = 0, right = nums.size() - 1;	
	while (left <= right) {
		int mid = left + (right - left) / 2;  
		// 防止溢出,right 可能是 INT_MAX,若是直接与 left 相加会有溢出的情况 
		if (nums[mid] < target) 
			left = mid + 1;
		else
			right = mid - 1;
			// nums[mid] >= target
	}
	return left;  
}

// 左闭右开区间:[left, right)
int lower_bound2(vector<int> nums, int target) {
	// 循环不变量:nums[left - 1] < target; nums[right] >= target 
	// 当 left == right 时,说明查找区间已经缩小到没有元素可供检查 
	
	int left = 0, right = nums.size();	
	while (left < right) {
		int mid = left + (right - left) / 2;  
		if (nums[mid] < target) 
			left = mid + 1;  
		else
			right = mid;
			// nums[mid] >= target
	}
	return left;    // return right; 也是可以的 
}

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

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

根据题目描述,我们要从非递减的数组给出 target 的开始位置和结束位置,且时间复杂度为 O ( l o g n ) O(log n) O(logn)。我们可以转变为,在数组中找到第一个 target 和 target+1 的开始位置,然后 target+1 元素对应的索引再减一就是 target 在数组的结束位置。
这个查找目标值的开始的位置和结束位置可以使用两次二分查找。其中结束位置 end 是基于开始位置 start 的,若 start 超过数组长度,或者 start=0,但是 nums[start] != target,相当于数组中不存在目标值,因此直接返回 {-1, -1},否则使用二分查找找到对应的 end 值。

// 左闭右闭区间:待查找的区间 [left, right] 
int lower_bound(vector<int> nums, int target) {
	int left = 0, right = nums.size() - 1;	
	while (left <= right) {
		int mid = left + (right - left) / 2;  
		if (nums[mid] < target) 
			left = mid + 1;
		else
			right = mid - 1;
	}
	return left;  
}

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int start = lower_bound(nums, target);

        if (start == nums.size() || nums[start] != target) 
            // target 大于要么小于 nums 的全部元素 
            return {-1, -1};
        
        int end = lower_bound(nums, target + 1) - 1;
        // 查找第一个大于或等于 target+1 的值,再 减一 就是目标值的结束位置
        
        return {start, end};
    }
};

第一个错误的版本

278. 第一个错误的版本

调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        int left = 1, right = n;
        
        while (left < right) {  // 注意
            int mid = left + (right - left) / 2;

            if (!isBadVersion(mid)) 
                left = mid+1;
            
            else right = mid;   // 注意
        }

        return left;  
        // 返回第一个错误版本; right == left ==> return right;  
    }
};

搜索插入位置

35. 搜索插入位置

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l = 0, r = nums.size()-1;

        while (l <= r) {
            int mid = l + (r - l) / 2;

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

        return l;
    }
};

二分查找

704. 二分查找

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0, r = nums.size()-1;

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

        return -1; 
    }
};

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

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

// 方式一: 暴力解
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        // 暴力解 
        if (nums1.size() == 0) {
            return count(nums2);
        }

        if (nums2.size() == 0) {
            return count(nums1);              
        }

        // 合并 
        nums1.insert(nums1.end(), nums2.begin(), nums2.end());
        // 排序
        sort(nums1.begin(), nums1.end());
        
        return count(nums1);
    }

    double count(vector<int> nums) {
        int size = nums.size();
        if (size % 2 == 0) 
            return (nums[size/2 - 1] + nums[size/2]) / 2.0;
        else 
            return nums[size/2]; 
    }
};
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值