【LeetCode 热题 100】二分查找 系列
📁 35. 搜索插入位置
将数组划分为两个区间[0 , left] [right , n],其中有 左区间的元素都小于targe,右区间的元素都大于等于target。
left指针维护左区间结束位置,right维护右区间的起始位置,right所在位置一定是第一个大于等于target的位置。
class Solution {
public:int searchInsert(vector<int>& nums, int target) {int left = 0 , right = nums.size();while(left < right){int mid = (left + right) >> 1;if(nums[mid] < target)left = mid + 1;else right = mid;}return left; 循环结束 此时left==right}
};
📁 74. 搜索二维矩阵
这一道题目还是类似上一道题目,只不过多加了一层循环。
class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int m = matrix.size() , n = matrix[0].size();for(int i = 0 ; i < m ; ++i){int left = 0 , right = n - 1;while(left < right){int mid = (left + right) >> 1;if(matrix[i][mid] < target)left = mid + 1;elseright = mid;}if(matrix[i][right] == target)return true;}return false;}
};
📁 34. 在排序数组中查找元素的第一个和最后一个位置
进行两次二分,寻找左区间的右端点 以及 寻找右区间的左端点。
class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if(nums.size() == 0)return {- 1 , -1};int n = nums.size();int left = 0 , right = n - 1;int begin = -1 , end = -1;while(left < right){int mid = (left + right) >> 1;if(nums[mid] < target)left = mid + 1;elseright = mid;}if(nums[left] != target)return {begin , end};begin = left , left = 0 , right = n - 1;while(left < right){int mid = (left + right + 1) >> 1;if(nums[mid] > target)right = mid - 1;elseleft = mid;}end = right;return {begin , end};}
};
📁 33. 搜索旋转排序数组
二分的条件就是: 有序或部分有序
1. 进行二分后,一定会后一个有序数组,有一个或有序或无序的数组,例如:
a. 4 5 6 [7] 0 1 2
b. 6 7 0 [1] 2 4 5
2. 根据有序数组,缩小搜索范围
class Solution {
public:int search(vector<int>& nums, int target) {int n = nums.size();int left = 0 , right = n - 1;while(left <= right){int mid = (left + right) >> 1;if(nums[mid] == target)return mid;if(nums[left] <= nums[mid]){if(target >= nums[left] && target <= nums[mid])right = mid - 1;else left = mid + 1;}else{if(target >= nums[mid + 1] && target <= nums[right])left = mid + 1;else right = mid - 1;}}return -1;}
};
📁 153. 寻找旋转排序数组中的最小值
将 mid 和 数组最后一个元素n 比较:
1. mid > n , 说明数组一定被划分成为两段,其中第一段的元素均大于第二段的元素
2. mid <= n , 说明x要么是最小值,要么最小值在右边(nums本身是递增数组, 只有一段)
int findMin(vector<int>& nums) {int left = 0 , right = nums.size() - 1;int x = nums[nums.size() - 1];while(left < right){int mid = (left + right) >> 1;if(nums[mid] > x)left = mid + 1;else right = mid;}return nums[left];}
📁 4. 寻找两个正序数组的中位数
规则: 使用一条分割线把两个数组分割成两个部分
1. 左边和右边的元素个数相等,或左边元素的个数比右边个数的多1个
2. 左边元素之和 <= 右边元素之和 (若不满足交叉小于等于的关系 就需要调整分割线的位置)
中位数一定就与分割切两侧的元素相关 确定这个红线使用二分查找
交叉小于等于:
arr1[分割线左侧元素] <= arr2[分割线右侧元素] && arr2[分割线左侧元素] <= arr1[分割线右侧元素]
因为需要通过访问分割线左右两边的元素,因此应该在较短的数组上确定分割线的位置
i: 第一个数组分割线右边的第一个元素的下标 i = 第一个数组分割线左侧元素的个数
j: 第二个数组分割线右边的第一个元素的下标 j = 第二个数组 分割线左侧元素的个数
class Solution {
public:double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {//需要访问 "中间分割线" 左右两边的元素,因此需要在较短的数组上确定 "中间分割线"的位置,避免越界if(nums1.size() > nums2.size()) {vector<int> tmp = nums1;nums1 = nums2;nums2 = tmp;}//通过元素个数较少的的数组确定分割线数量,避免数组越界int m = nums1.size() , n = nums2.size();int totalLeft = (m + n + 1) / 2; //表示合并后数组中左半部分的元素数量int left = 0 , right = m;while(left < right){int i = left + (right - left + 1) / 2;int j = totalLeft - i;if(nums1[i - 1] > nums2[j])right = i - 1;elseleft = i; }int i = left , j = totalLeft - i;//处理边界情况int num1LeftMax = i == 0 ? INT_MIN : nums1[i - 1];int num1RightMin = i == m ? INT_MAX : nums1[i];int num2LeftMax = j == 0 ? INT_MIN : nums2[j-1];int num2RightMin = j == n ? INT_MAX : nums2[j];if((m + n ) % 2 == 1)return max(num1LeftMax , num2LeftMax);elsereturn (double)((max(num1LeftMax , num2LeftMax) + min(num1RightMin , num2RightMin))) / 2;}
};