目录
- 二分法细节
- 1、leetcode 34 在排序数组中查找元素的第一个和最后一个位置
- 2、不完全有序下的二分查找(leetcode33. 搜索旋转排序数组)
- 3、含重复元素的不完全有序下的二分查找(81. 搜索旋转排序数组 II)
- 3、不完全有序下的找最小元素(153. 寻找旋转排序数组中的最小值)
- 4、二维矩阵二分(74. 搜索二维矩阵)
二分法细节
1、计算mid时,不能使用(left + right)/2
,否则有可能计算溢出。
可以使用下面方法计算:
mid = left + ((right - left) >> 1)
2、while(left <= right)
,注意符号为 <=
如果是 left < right,则当我们执行到最后一步,left与right重叠时,则会跳出循环。但是事实是,此时的left和right指向的可能就是我们的目标元素。
3、 left = mid + 1,right = mid - 1;
如果设置left = mid ,right = mid,left=1,right = 8,mid = 7,会陷入死循环,mid一直为7.
1、leetcode 34 在排序数组中查找元素的第一个和最后一个位置
https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
在普通的二分上加上检索区间的左右边界,需要注意特殊的输入情况。
class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if(nums.empty()) return{-1,-1};int left = 0;int right = nums.size() - 1;while(left <= right){int mid = left + ((right - left) >> 1);if(nums[mid] == target){int l = mid;int r = mid;//寻找左边界//寻找右边界while( l >= 0 && nums[l] == target) l--;while(r <= nums.size() - 1 && nums[r] == target) r++;l += 1;r -= 1;if(l < 0) l = 0;if(r > nums.size() - 1) r = nums.size() - 1;return {l,r};}else if(nums[mid] > target){right = mid -1;}else{left = mid + 1;}}return {-1,-1};}
};
2、不完全有序下的二分查找(leetcode33. 搜索旋转排序数组)
https://leetcode-cn.com/problems/search-in-rotated-sorted-array/
升序排列的整数数组 nums 在预先未知的某个点上进行了旋转(例如, [0,1,2,4,5,6,7] 经旋转后可能变为 [4,5,6,7,0,1,2] )。
请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
注意这里的数组中不包含重复的元素。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1
之前使用的二分都是在数组是有序的背景下,如果不完全有序时也是可以使用二分查找的。
已知分开来的两个子数组都是有序递增的。那么如果nums[mid]>= nums[left]
则说明mid和left一定落在同一个子数组里,反之nums[mid]< nums[left]
,说明mid和left不在同一个子数组里,并且此时可以肯定left在数组1,mid在数组2。
注意这里的mid还是通过left和right的下标来求得的,也就是说mid还是在left与right之间。
如果mid和left在同一个子数组中,那么target一共有2种分布的可能:
1、target在mid左边,此时有target >= nums[left] && target < nums[mid]
,令right = mid - 1,这样就在有序的数组1中进行寻找了
2、target在mid右边,此时有 target > nums[mid] || target < nums[left]
,令left = mid + 1,缓慢地将left和right指针驱赶到同一个有序区间内。
如果mid和right在同一个子数组中,那么target一共有2种分布的可能:
1、target <= nums[right] && target > nums[mid]
此时查找部分就落在右半部分,令left= mid + 1,这样就在有序的数组2中进行寻找了
2、target > nums[right] || target < nums[mid]
此时mid落在左半部分,应该令right = mid - 1,将两个指针驱赶到同一个有序区间内
AC代码
class Solution {
public:int search(vector<int>& nums, int target) {int left = 0;int right = nums.size() - 1;while(left <= right){int mid = left + ((right - left) >> 1);if(nums[mid] == target){return mid;}//left and mid 落在同一个数组if(nums[mid] >= nums[left]){//mid和left一个数组,target在左边数组if(target >= nums[left] && target < nums[mid]){right = mid - 1;}//mid和right一个数组else if(target > nums[mid] || target < nums[left]){left = mid + 1;}}//left and mid 落在不同数组else if(nums[mid] < nums[left]){//target在mid 和 right之间if(nums[mid] < target && target <= nums[right]){left = mid + 1;}//target在left 和mid之间else if(target > nums[right] || target < nums[mid]){right = mid - 1;}}}//没有查找到return -1;}
};
这一题思路还是比较麻烦的,不易想到,还需要加深理解才行。
3、含重复元素的不完全有序下的二分查找(81. 搜索旋转排序数组 II)
https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。
示例 1:
输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true
示例 2:
输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false
进阶:
这是 搜索旋转排序数组 的延伸题目,本题中的 nums 可能包含重复元素。
这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?
如果我们直接套用之前的思路会发现出现下面的问题:
原因就是nums[left] == nums[mid]时,这里可能会有问题。
这时我们需要让left++即可。注意在书写代码的时候我们需要在left++后这次循环就结束,所以我们把nums[left] == nums[mid]的情况放到最后处理即可:
class Solution {
public:int search(vector<int>& nums, int target) {int left = 0;int right = nums.size() - 1;while(left <= right){int mid = left + ((right - left) >> 1);if(nums[mid] == target){return true;}//left and mid 落在同一个数组if(nums[mid] > nums[left]){//mid和left一个数组,target在左边数组if(target >= nums[left] && target < nums[mid]){right = mid - 1;}//mid和right一个数组else if(target > nums[mid] || target < nums[left]){left = mid + 1;}}//left and mid 落在不同数组else if(nums[mid] < nums[left]){//target在mid 和 right之间if(nums[mid] < target && target <= nums[right]){left = mid + 1;}//target在left 和mid之间else if(target > nums[right] || target < nums[mid]){right = mid - 1;}}elseleft++;}//没有查找到return false;}
};
3、不完全有序下的找最小元素(153. 寻找旋转排序数组中的最小值)
https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/
假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。
请找出其中最小的元素。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
示例 3:
输入:nums = [1]
输出:1
由于跳变点小于左边的值,大于右边的值。所以当nums[mid] < nums[left]时,说明跳变点在mid与left之间,因为mid与right之间必然是递增的(跳变只有一次)。
当nums[mid] > nums[left]说明mid与left之间是单调递增的。
当nums[mid] == nums[left]说明此时mid与left重合了,此时需要将left向右移动1格,然后重新迭代。
class Solution {
public:int findMin(vector<int>& nums) {if(nums.size() == 1) return nums[0];int left = 0;int right = nums.size() - 1;while(left <= right){if(nums[left] <= nums[right]) return nums[left];int mid = left + ((right - left) >> 1);if(nums[mid] < nums[left]) right = mid;else if(nums[mid] > nums[left]) left = mid + 1;else if(left == mid) left++;}return -1;}
};
4、二维矩阵二分(74. 搜索二维矩阵)
https://leetcode-cn.com/problems/search-a-2d-matrix/
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 3
输出:true
示例 2:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 13
输出:false
示例 3:
输入:matrix = [], target = 0
输出:false
只要将二维数组展开成一维数组即可,用普通的二分。
需要注意的是将mid转换成行列坐标;
mid/col 得到的是行数
mid % col 得到的是列数
class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {//获得行数int row = matrix.size();if(row == 0) return false;//获得列数int col = matrix[0].size();int left = 0;int right = row * col - 1;while(left <= right){int mid = left + ((right - left) >> 1);if(target == matrix[mid/col][mid%col]) return true;else if(target > matrix[mid/col][mid%col]) left = mid + 1;else right = mid - 1;}return false;}
};