目录
1、704. 二分查找
2、34. 在排序数组中查找元素的第一个和最后一个位置
3、69. x的平方根
4、35. 搜索插入位置
5、852. 山脉数组的峰顶索引
6、162. 寻找峰值
7、153. 寻找旋转排序数组中的最小值
8、LCR 173. 点名
1、704. 二分查找
class Solution {
public:int search(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 if (nums[mid] > target)right = mid - 1;elsereturn mid;}return -1;}
};
思路:
首先,定义了两个变量left
和right
,分别表示数组的左边界和右边界。初始时,left
被设置为数组的第一个元素的索引(0),right
被设置为数组的最后一个元素的索引(nums.size() - 1
)。
然后,使用一个循环来执行二分查找。循环条件是left
小于等于right
,这表示还存在未查找的区间。在二分查找算法中,使用left <= right
作为循环条件是为了处理以下两种情况:
-
当查找区间只剩下一个元素时,即
left
和right
指向同一个位置时,仍然需要进行一次比较。如果使用left < right
作为循环条件,那么在这种情况下循环会提前结束,导致无法正确判断目标值是否存在。 -
当查找区间为空时,即
left
大于right
时,循环条件为假,可以直接退出循环。这种情况下,表示目标值不存在于数组中。
在每次循环中,首先计算中间元素的索引mid
,通过将左边界和右边界与左边界之差相加除以2得到。这样可以将查找区间缩小一半,并且不会超出整形范围。
接下来,通过比较中间元素nums[mid]
和目标值target
的大小关系,来确定下一步的查找方向。
- 如果
nums[mid]
小于target
,说明目标值在右半部分,将left
更新为mid + 1
,缩小查找区间为右半部分。 - 如果
nums[mid]
大于target
,说明目标值在左半部分,将right
更新为mid - 1
,缩小查找区间为左半部分。 - 如果
nums[mid]
等于target
,说明找到了目标值,直接返回mid
作为结果。
如果循环结束时仍然没有找到目标值,即left
大于right
,则返回-1表示未找到。
2、34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if (nums.size() == 0)return {-1, -1};int left = 0, right = nums.size() - 1;int begin = 0;// 使用二分查找找到目标值的最左边界while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] < target)left = mid + 1;elseright = mid;}// 检查最左边界的元素是否为目标值if (nums[left] != target)return {-1, -1};elsebegin = left;left = 0, right = nums.size() - 1;// 使用二分查找找到目标值的最右边界while (left < right) {int mid = left + (right - left + 1) / 2;if (nums[mid] <= target)left = mid;elseright = mid - 1;}return {begin, right};}
};
思路:寻找左右边界
首先,检查数组是否为空,如果为空,则直接返回{-1, -1}表示未找到目标值。
初始化左边界left
为0,右边界right
为数组长度减1。
使用二分查找来找到目标值的最左边界。循环条件是left < right
。
- 在每次循环中,计算中间元素的索引
mid
。 - 比较中间元素
nums[mid]
与目标值target
的大小关系:- 如果
nums[mid]
小于目标值,说明目标值在右半部分,将left
更新为mid + 1
,缩小查找区间为右半部分。 - 如果
nums[mid]
大于等于目标值,说明目标值在左半部分,将right
更新为mid
,缩小查找区间为左半部分。
- 如果
- 检查最左边界的元素是否为目标值。如果不是目标值,则表示未找到目标值,直接返回{-1, -1}。
将最左边界left
赋值给begin
,作为目标值的起始位置。
重新初始化left
和right
,继续使用二分查找来找到目标值的最右边界。循环条件是left < right
。
- 在每次循环中,计算中间元素的索引
mid
,。 - 比较中间元素
nums[mid]
与目标值target
的大小关系:- 如果
nums[mid]
小于等于目标值,将left
更新为mid
,缩小查找区间为右半部分。 - 如果
nums[mid]
大于目标值,将right
更新为mid - 1
,缩小查找区间为左半部分。
- 如果
- 返回结果为{begin, right},其中
begin
为目标值的最左边界,right
为目标值的最右边界。
3、69. x的平方根
思路:寻找左右边界
首先,检查输入的数x
是否小于1,如果是,则直接返回0。
然后,定义了两个变量left
和right
,分别表示查找区间的左边界和右边界。初始时,left
被设置为1,right
被设置为x
。
接下来,使用二分查找来逼近平方根的值。循环条件是left < right
,这表示还存在未查找的区间。
在每次循环中,首先计算中间元素的值mid
,通过将左边界和右边界相加除以2得到。这里使用了(right - left + 1) / 2
来确保向上取整,以保证查找区间向上移动。
然后,通过比较中间元素的平方mid * mid
与目标值x
的大小关系,来确定下一步的查找方向。
- 如果
mid * mid
小于等于x
,说明平方根在右半部分,将left
更新为mid
,缩小查找区间为右半部分。 - 如果
mid * mid
大于x
,说明平方根在左半部分,将right
更新为mid - 1
,缩小查找区间为左半部分。
最后,循环结束时,left
和right
相等,表示找到了平方根的整数部分。返回left
作为结果。
class Solution {
public:int mySqrt(int x) {if (x < 1)return 0;int left = 1, right = x;while (left < right) {long long mid = left + (right - left + 1) / 2;if (mid * mid <= x)left = mid;elseright = mid - 1;}return left;}
};
4、35. 搜索插入位置
思路:寻找左边界
class Solution {
public:int searchInsert(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;elseright = mid;}if (nums[left] < target)return right + 1;elsereturn right;}
};
思路:寻找右边界
class Solution {
public:int searchInsert(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left < right) {int mid = left + (right - left + 1) / 2;if (nums[mid] <= target)left = mid;elseright = mid - 1;}if (nums[left] < target)return right + 1;elsereturn right;}
};
5、852. 山脉数组的峰顶索引
思路:寻找左边界
class Solution {
public:int peakIndexInMountainArray(vector<int>& arr) {int left = 1, right = arr.size() - 2;while (left < right) {int mid = left + (right - left) / 2;if (arr[mid] < arr[mid + 1])left = mid + 1;elseright = mid;}return left;}
};
思路:寻找右边界
class Solution {
public:int peakIndexInMountainArray(vector<int>& arr) {int left = 1, right = arr.size() - 2;while (left < right) {int mid = left + (right - left + 1) / 2;if (arr[mid] > arr[mid - 1])left = mid;elseright = mid - 1;}return left;}
};
两个解决方案都是有效的,并且都具有 O(log(n)) 的时间复杂度。它们的区别在于二分查找的条件判断。
- 第一个解决方案中,二分查找的条件判断是
arr[mid] > arr[mid - 1]
,即判断当前位置的元素是否大于前一个元素。如果满足这个条件,则说明峰值在当前位置或右侧,将left
更新为mid
;否则,峰值在左侧,将right
更新为mid - 1
。 - 第二个解决方案中,二分查找的条件判断是
arr[mid] < arr[mid + 1]
,即判断当前位置的元素是否小于后一个元素。如果满足这个条件,则说明峰值在右侧,将left
更新为mid + 1
;否则,峰值在左侧或当前位置,将right
更新为mid
。
6、162. 寻找峰值
思路:寻找左边界
class Solution {
public:int findPeakElement(vector<int>& nums) {int left = 0, right = nums.size() - 1;while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] < nums[mid + 1])left = mid + 1;elseright = mid;}return left;}
};
思路:寻找右边界
class Solution {
public:int findPeakElement(vector<int>& nums) {int left = 0, right = nums.size() - 1;while (left < right) {int mid = left + (right - left + 1) / 2;if (nums[mid] > nums[mid - 1])left = mid;elseright = mid - 1;}return left;}
};
7、153. 寻找旋转排序数组中的最小值
思路:以最右端点为基准点进行比较,逐渐缩小范围。
class Solution {
public:int findMin(vector<int>& nums) {int left = 0, right = nums.size() - 1;int x = nums[right];while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] > x)left = mid + 1;elseright = mid;}return nums[left];}
};
8、LCR 173. 点名
思路:根据数组元素与下标相等和不相等划分两个区间,最后,如果元素与其下标不相等,则返回其下标即为所缺元素;如果元素与其下标相等,则所缺元素为数组最大元素加一,即为left或right加一。
class Solution {
public:int takeAttendance(vector<int>& records) {int left = 0, right = records.size() - 1;while (left < right) {int mid = left + (right - left) / 2;if (records[mid] == mid)left = mid + 1;elseright = mid;}return left == records[left] ? left + 1 : left;}
};