目录
[x 的平方根(原题链接)](#x 的平方根(原题链接))
二分查找(原题链接)
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
解题思路
该算法实现的是二分查找算法,用于在一个有序数组中查找目标值的位置。二分查找的核心思想是每次将查找范围缩小一半,从而在对数时间内找到目标值。
步骤说明
初始化指针:
left
指向数组的起始位置,right
指向数组的末尾位置。这样定义指针是为了确定搜索区间。进入循环:
- 当
left
指针小于等于right
指针时,继续循环。循环的目的是不断缩小查找区间,直到找到目标值或区间无效。计算中间位置:
- 计算
mid
的位置作为当前查找的中间位置,使用(left + right) / 2
的方式,但为了避免整数溢出,改用left + (right - left) / 2
。判断中间值:
- 如果
nums[mid]
等于目标值target
,直接返回mid
,表示找到了目标值。- 如果
nums[mid]
大于目标值,说明目标值在左半部分,将right
指针移动到mid - 1
。- 如果
nums[mid]
小于目标值,说明目标值在右半部分,将left
指针移动到mid + 1
。返回结果:
- 如果在循环结束后仍未找到目标值,则返回 -1,表示数组中不存在目标值。
具体代码class Solution
{
public:
int search(vector<int>& nums, int target)
{
// 初始化 left 与 right 指针
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;
}
// 如果未找到目标值,返回 -1
return -1;
}
};
在排序数组中查找元素的第一个和最后一个位置(原题链接)
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
解题思路
该算法的目的是找到目标值在有序数组中的起始位置和结束位置。为了达到这个目的,算法使用了二分查找的方法,分别寻找目标值的左端点和右端点。由于二分查找在有序数组中可以高效地定位目标值,因此这种方法非常适合这个问题。
步骤说明
处理边界情况:
- 如果数组为空,直接返回
{-1, -1}
。查找左端点:
- 使用二分查找找到目标值的第一个出现位置(左端点)。如果
nums[mid]
小于目标值,说明左半部分不可能包含目标值,移动左指针。否则,右半部分可能包含目标值或正好是目标值,因此右指针移动到mid
。- 最后检查
nums[left]
是否等于目标值来验证是否找到目标值。查找右端点:
- 使用二分查找找到目标值的最后一个出现位置(右端点)。为了保证找到的右端点是最后一个目标值的位置,计算
mid
时取(left + (right - left + 1) / 2)
。如果nums[mid]
小于等于目标值,左指针移动到mid
;否则,右指针移动到mid - 1
。- 由于二分查找结束时
left
指向目标值的最后一个位置,因此返回{begin, right}
,其中begin
是左端点,right
是右端点。
具体代码class Solution
{
public:
vector<int> searchRange(vector<int>& nums, int target)
{
// 处理边界情况:如果数组为空,直接返回 {-1, -1}
if (nums.empty())
return {-1, -1};int begin = 0; // 1. 二分查找左端点 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; // 目标值在左半部分或正好是中间值 } // 检查找到的左端点是否为目标值 if (nums[left] != target) return {-1, -1}; // 目标值不存在 else begin = left; // 标记左端点 // 2. 二分查找右端点 left = 0, right = nums.size() - 1; while (left < right) { int mid = left + (right - left + 1) / 2; if (nums[mid] <= target) left = mid; // 目标值在右半部分或正好是中间值 else right = mid - 1; // 目标值在左半部分 } // 返回目标值的起始和结束位置 return {begin, right}; }
};
搜索插入位置(原题链接)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
解题思路
这个算法的目标是找到目标值
target
应该插入到有序数组nums
的位置,使得插入后的数组仍然保持有序。这个问题可以通过修改的二分查找算法来解决。算法会找到目标值插入的位置,确保所有小于目标值的元素在它前面,所有大于目标值的元素在它后面。
步骤说明
初始化指针:
- 使用两个指针
left
和right
来表示当前查找的区间,初始值分别为数组的起始位置和末尾位置。二分查找:
- 在
left
小于right
时,计算中间位置mid
。- 如果
nums[mid]
小于目标值target
,说明目标值在右半部分,因此将left
指针移动到mid + 1
。- 如果
nums[mid]
大于或等于目标值,说明目标值在左半部分或正好是中间值,因此将right
指针移动到mid
。确定插入位置:
- 循环结束时,
left
应该是目标值应该插入的位置。如果nums[left]
小于目标值,则right
+ 1 为插入位置;否则,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) / 2; if (nums[mid] < target) left = mid + 1; // 目标值在右半部分 else right = mid; // 目标值在左半部分或正好是中间值 } // 确定目标值的插入位置 // 如果 nums[left] 小于目标值,插入位置是 right + 1 // 否则,插入位置是 right if (nums[left] < target) return right + 1; return right; }
};
x 的平方根(原题链接)
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意: 不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
解题思路
要找到一个非负整数
x
的平方根,我们可以使用二分查找算法。目标是找到最大的整数y
,使得y^2
小于或等于x
。在有序的整数区间[0, x]
上进行二分查找可以有效地找到这个整数y
。
步骤说明
处理边界情况:
- 如果
x
小于 1,则平方根应该是 0(这是因为平方根在负数或零范围内的定义)。初始化指针:
left
初始化为 1(对于x >= 1
的情况),right
初始化为x
。这些指针定义了二分查找的区间。二分查找:
- 使用二分查找来找到最大整数
y
,使得y^2 <= x
。计算中间位置mid
时使用(left + (right - left + 1) / 2)
来避免整数溢出。- 如果
mid * mid
小于或等于x
,说明mid
可以是平方根,或者在mid
右侧的值也有可能是平方根,因此移动左指针到mid
。- 否则,目标平方根只能在
mid
的左侧,因此移动右指针到mid - 1
。返回结果:
- 循环结束时,
left
就是最大整数,使得left^2
小于或等于x
,即所求的平方根。
具体代码class Solution
{
public:
int mySqrt(int x)
{
// 处理边界情况:x 小于 1 的平方根是 0
if (x < 1)
return 0;int left = 1, right = x; // 二分查找平方根 while (left < right) { // 防止中间值计算溢出 long long mid = left + (right - left + 1) / 2; // 检查 mid^2 是否小于等于 x if (mid * mid <= x) left = mid; // mid 可能是答案,尝试右边 else right = mid - 1; // mid 不是答案,调整右边界 } // 返回找到的最大平方根整数 return left; }
};
山脉数组的峰顶索引(原题链接)
给定一个长度为 n
的整数 山脉 数组 arr
,其中的值递增到一个 峰值元素 然后递减。
返回峰值元素的下标。
你必须设计并实现时间复杂度为 O(log(n))
的解决方案。
解题思路
山脉数组的特性是存在一个峰值元素,使得在峰值左边的元素严格递增,右边的元素严格递减。我们可以利用二分查找来高效地找到这个峰值。通过比较中间元素与其相邻的元素,我们可以判断当前元素是在上升序列中还是下降序列中,从而调整搜索区间。
步骤说明
初始化指针:
left
初始化为 0,right
初始化为数组的最后一个索引。这两个指针用于定义搜索区间。二分查找:
- 计算中间位置
mid
,为了防止溢出,使用left + (right - left + 1) / 2
。- 判断
arr[mid]
和arr[mid - 1]
的关系:
- 如果
arr[mid] > arr[mid - 1]
,说明mid
处于山脉的上升部分,峰值在mid
右侧或者就是mid
,因此移动left
指针到mid
。- 如果
arr[mid] <= arr[mid - 1]
,说明mid
处于山脉的下降部分,峰值在mid
左侧,因此移动right
指针到mid - 1
。返回结果:
- 当
left == right
时,left
和right
指向的就是峰值元素的索引。
具体代码class Solution
{
public:
int peakIndexInMountainArray(vector<int>& arr)
{
int left = 0, right = arr.size() - 1;// 二分查找山脉数组的峰值 while (left < right) { // 防止溢出计算中间位置 int mid = left + (right - left + 1) / 2; // 判断 mid 是否处于上升部分 if (arr[mid] > arr[mid - 1]) left = mid; // 峰值在右侧或就是 mid else right = mid - 1; // 峰值在左侧 } // 返回找到的峰值索引 return left; }
};
寻找峰值(原题链接)
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums
,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞
。
你必须实现时间复杂度为 O(log n)
的算法来解决此问题。
解题思路
在一个无序数组中寻找峰值元素,可以利用二分查找的思想。通过比较中间元素与其右侧元素的大小,可以判断峰值所在的区域,从而逐步缩小查找范围。最终找到一个峰值元素的索引。
步骤说明
初始化指针:
left
初始化为数组的起始位置 0,right
初始化为数组的末尾位置nums.size() - 1
。二分查找:
- 在
left
小于right
的情况下,继续循环。- 计算中间位置
mid
,为防止溢出,使用left + (right - left) / 2
。- 判断
nums[mid]
和nums[mid + 1]
的关系:
- 如果
nums[mid] > nums[mid + 1]
,说明在mid
位置的左侧可能存在峰值(包括mid
),因此移动right
指针到mid
。- 如果
nums[mid] <= nums[mid + 1]
,说明在mid
位置的右侧存在峰值,因此移动left
指针到mid + 1
。返回结果:
- 当
left
和right
相等时,循环结束,left
即为峰值元素的索引。
具体代码class Solution
{
public:
int findPeakElement(vector<int>& nums)
{
int left = 0, right = nums.size() - 1;// 二分查找峰值元素 while (left < right) { // 防止溢出计算中间位置 int mid = left + (right - left) / 2; // 判断 mid 是否处于下降坡度 if (nums[mid] > nums[mid + 1]) right = mid; // 峰值在左侧,包括 mid 位置 else left = mid + 1; // 峰值在右侧 } // 返回找到的峰值索引 return left; }
};
寻找旋转排序数组中的最小值(原题链接)
已知一个长度为 n
的数组,预先按照升序排列,经由 1
到 n
次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,2]
- 若旋转
7
次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。
给你一个元素值 互不相同 的数组 nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
解题思路
旋转排序数组中的最小元素是所有元素中最小的值,且它右侧的元素都大于它。利用二分查找,可以在 O(log n) 时间复杂度内找到这个最小值。关键是判断中间元素的位置,以确定缩小搜索区间的方向。
步骤说明
初始化指针:
left
初始化为数组的起始位置 0,right
初始化为数组的末尾位置nums.size() - 1
。选择基准值:
- 选择数组末尾的元素
nums[right]
作为基准值x
。在未旋转的情况下,最小值就是这个基准值。二分查找:
- 在
left
小于right
时,继续循环。- 计算中间位置
mid
,为防止溢出,使用left + (right - left) / 2
。- 如果
nums[mid] > x
,说明最小元素在右侧,因此left
移动到mid + 1
。- 如果
nums[mid] <= x
,说明最小元素在左侧或中间位置,因此right
移动到mid
。返回结果:
- 当
left
和right
相等时,循环结束,此时left
指向的元素就是最小元素。
具体代码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; // 判断 mid 位置的元素与基准值的关系 if (nums[mid] > x) left = mid + 1; // 最小元素在右侧 else right = mid; // 最小元素在左侧或 mid 位置 } // 返回最小元素 return nums[left]; }
};
点名(原题链接)
某班级 n 位同学的学号为 0 ~ n-1。点名结果记录于升序数组 records
。假定仅有一位同学缺席,请返回他的学号。
解题思路
这段代码的主要目的是在一个有序数组
records
中查找一个满足特定条件的位置。具体来说,数组records
中的元素表示某种记录,每个元素的值表示某种"出勤"记录。在这个问题中,要求找到一个位置i
使得records[i]
不等于i
,并返回该位置。如果所有位置i
都满足records[i] == i
,则返回数组的长度。
步骤说明
初始化指针 : 定义两个指针
left
和right
,分别指向数组的开头和结尾。二分查找 : 使用二分查找的方法逐步缩小查找范围。通过计算
mid
,即中间位置的索引,来分割数组。条件判断:
- 如果
records[mid] == mid
,说明在mid
及其左侧的元素都满足records[i] == i
,因此将left
移动到mid + 1
继续查找。- 如果
records[mid] != mid
,则可能的解在mid
及其左侧,因此将right
移动到mid
。返回结果 : 最后,检查
left
和records[left]
的关系:
- 如果
left == records[left]
,则说明找到了一个最后满足records[i] == i
的位置,返回left + 1
。- 否则,返回
left
作为结果。
具体代码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; // 更新left指针,因为此时mid及左边的元素都满足条件
else
right = mid; // 更新right指针,因为可能的解在mid及左边
}
// 最终检查left位置是否满足条件,返回结果
return left == records[left] ? left + 1 : left;
}
};