二分查找题目
- LeetCode二分查找题目
- 1. Leetcode-704 二分查找
- 2. Leetcode-35 搜索插入的位置
- 3. Leetcode-69 X 的平方根
- 4. Leetcode-367 有效的完全平方数
- 5. Leetcode-34 在排序数组中查找元素的第一个和最后一个位置
- 6. Leetcode-153 寻找旋转排序数组中的最小值
- 7. Leetcode-154 寻找旋转排序数组中的最小值 II
- 8. Leetcode-278 第一个错误的版本
- 9. Leetcode-162 寻找峰值
- 10. Leetcode-33 搜索旋转排序数组
- 11. Leetcode-33 搜索旋转排序数组 II
- 12. Leetcode-658 找到 K 个最接近的元素
LeetCode二分查找题目
1. Leetcode-704 二分查找
题目
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
题解
class Solution
{
public:
int search(vector<int> &nums, int target)
{
int left = 0, right = nums.size() - 1;
while (left <= right) // 定义target在左闭右闭的区间[left, right]内
{
int mid = left + (right - left) / 2;
// 防止溢出,等同于mid = (left + right) / 2
if (nums[mid] > target)
right = mid - 1;
else if (nums[mid] < target)
left = mid + 1;
else
return mid;
}
return -1;
}
};
2. Leetcode-35 搜索插入的位置
题目
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
题解思路
原先的二分查找是查找数组中的值,在此处则是查找插入的位置下标,因此只需对标准二分查找进行修改即可:
定义 target 在区间[left, right]
中,同样地不断地对数组进行二分查找,查找到第一个大于等于 target 的元素的值即可
。若 mid 处的值小于 target,则 left = mid + 1;若 mid 处的值大于等于 target,则 right = mid - 1,循环到最后,left 一定指向第一个大于或等于 target 的元素的位置!
根据 if 的判断条件,left 左边的值一直保持小于 target,right 右边的值一直保持大于等于 target,而且 left 最终一定等于 right + 1,这么一来,循环结束后,在 left 和 right 之间画一条竖线,恰好可以把数组分为两部分:left 左边的部分和 right 右边的部分,而且 left 左边的部分全部小于 target,并以 right 结尾;right 右边的部分全部大于等于 target,并以 left 为首,因此 left 一定指向目标值要插入的位置!
题解
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) // nums[mid] < target
left = mid + 1;
else // nums[mid] >= target
right = mid - 1;
}
return left;
}
};
3. Leetcode-69 X 的平方根
题目
给你一个非负整数 x ,计算并返回 x 的算术平方根。
由于返回类型是整数,结果只保留整数部分,小数部分将被舍去。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
题解思路 1——二分查找
由于 x 的平方根的整数部分是满足 k^2 <= x 的最大整数,利用整数是有序且无重复的特点,可以对 k 进行二分查找
题解 1
class Solution
{
public:
int mySqrt(int x)
{
int left = 0, right = x, ans = -1;
while(left <= right)
{
int mid = left + (right - left) / 2;
if((long long) mid * mid <= x) // 防止溢出
{
ans = mid;
left = mid + 1;
}
else
right = mid - 1;
}
return ans;
}
};
题解思路 2——牛顿迭代公式
牛顿迭代法是一种可以用来快速求解函数零点的方法。
![[牛顿迭代公式.png]]
在这题求 X 的平方根,可以转化成求函数的零点!
![[求平方根牛顿迭代递推公式.png]]
题解 2
class Solution
{
public:
int mySqrt(int x)
{
double x0 = x, C = x;
double xi;
while(true)
{
xi = 0.5 *(x0 + C / x0); // 迭代公式
if(fabs(x0 - xi) < 1e-7) // 精度
break;
x0 = xi; // 推进迭代
}
return (int)x0;
}
};
4. Leetcode-367 有效的完全平方数
题目
给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
完全平方数是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt。
示例 1:
输入:num = 16
输出:true
解释:返回 true ,因为 4 * 4 = 16 且 4 是一个整数。
示例 2:
输入:num = 14
输出:false
解释:返回 false ,因为 3.742 * 3.742 = 14 但 3.742 不是一个整数。
题解
class Solution
{
public:
bool isPerfectSquare(int num)
{
int left = 0, right = num;
while(left <= right)
{
int mid = left + (right - left) / 2;
if((long long) mid * mid < num)
left = mid + 1;
else if((long long) mid * mid > num)
right = mid - 1;
else
return true;
}
return false;
}
};
5. Leetcode-34 在排序数组中查找元素的第一个和最后一个位置
题目
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
题解思路
显然这题应该使用二分查找,只是查找的数组中是存在重复元素的。题目要求找出数组中的目标元素出现的开始和结束位置,其实就是查找大于等于目标元素的第一个位置和小于等于目标元素的最后一个位置
,因此可以编写一个查找大于等于目标元素的二分查找函数,其返回值为第一个大于等于目标元素的位置leftIndex,再调用该函数查找大于等于目标元素+1的第一个位置rightIndex,rightIndex - 1即为目标元素在数组中的的最后一个位置。
题解
class Solution
{
public:
int search(vector<int> &nums, int target) // 查找第一个>=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;
}
vector<int> searchRange(vector<int> &nums, int target)
{
vector<int> ans(2, -1);
int leftIndex = search(nums, target);
// 查找第一个>= target的元素的位置,即为target在数组中出现的第一个位置
if (leftIndex >= nums.size() || nums[leftIndex] != target)
return ans;
int rightIndex = search(nums, target + 1);
// 查找第一个>= target + 1的元素的位置,即为target在数组中的最后一个位置的下一个位置
ans[0] = leftIndex;
ans[1] = rightIndex - 1;
return ans;
}
};
6. Leetcode-153 寻找旋转排序数组中的最小值
题目
已知一个长度为 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) 的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。
示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
题解思路 1
虽然旋转后的数组是无序的,看似不能使用二分查找,但是我们仔细分析就可以发现,要查找的最小值具有如下性质:要查找的旋转数组中的最小值其实就是第一个小于数组中的最后一个元素的元素!
因此可以利用该性质进行二分查找。
题解 1
class Solution
{
public:
int findMin(vector<int> &nums)
{
int left = 0, right = nums.size() - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < nums[nums.size() - 1])
// 查找第一个小于数组最后一个元素的元素
right = mid;
else
left = mid + 1;
}
return nums[left];
}
};
题解思路 2
对于旋转数组中的最小值,它具有如下特点:它小于它右边的所有元素,也小于它左边的所有元素,同时旋转数组的左半边的元素都要大于右半边的最后一个元素,因此可以对最小值不断进行二分查找来找出最小值。定义最小值在区间[left, right]
中,left 处于旋转数组左半边,right 处于旋转数组右半边,区间中间索引 mid = (left + right) / 2,由于本题数组元素都是不重复的,因此有:
nums[mid] < nums[right]
时,此时说明 mid 在最小值的右侧,因此忽略右区间,直接搜索左区间,但也有可能 mid 处元素就是最小值,因此,right = mid;(注意不是 mid - 1!);nums[mid] > nums[right]
时,此时说明 mid 在最小值的左侧,因此忽略左区间,直接搜索右区间,并且由于 mid 处的元素都已经大于 right 处元素了,因此 mid 必不可能是最小值, left = mid + 1;nums[mid] == nums[right]
,这种情形不可能发生,因为本题的数组元素都是不重复的。
题解 2
class Solution
{
public:
int findMin(vector<int> &nums)
{
int left = 0;
int right = nums.size() - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < nums[right])
// nums[mid] < nums[right],即mid是在最小值的右侧,因此需要往左区间查找
right = mid;
else
left = mid + 1;
// nums[mid] > nums[right],即mid是在最小值的左侧,因此需要往右区间查找
}
return nums[left];
}
};
7. Leetcode-154 寻找旋转排序数组中的最小值 II
题目
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次旋转后,得到输入数组。
例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
若旋转 7 次,则可以得到 [0,1,4,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 ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的最小元素。
你必须尽可能减少整个过程的操作步骤。
示例 1:
输入:nums = [1,3,5]
输出:1
示例 2:
输入:nums = [2,2,2,0,1]
输出:0
题解思路
题解思路同上题,只是需要注意的是,本题数组元素可能重复,因此可能存在nums[mid] == nums[right]
这一种情况。
当 nums[mid] == nums[right]
时,我们无法知晓此时 mid 到底是处于数组的左半边还是右半边从而忽略一部分元素,但可以知晓的是,由于 nums[mid]与 nums[right]值相同,因此无论 right 是否是最小值,此时都可以忽略 right 处的元素,因为有 nums[mid]元素作为代替。
题解
class Solution
{
public:
int FindMin(vector<int>& nums)
{
int left = 0, right = nums.size() - 1;
while(left < right)
{
int mid = left + (right - left) / 2;
if(nums[mid] < nums[right])
// nums[mid] < nums[right],即mid是在最小值的右侧,因此需要往左区间查找
right = mid;
else if(nums[mid] > nums[right])
// nums[mid] > nums[right],即mid是在最小值的左侧,因此需要往右区间查找
left = mid + 1;
else
// nums[mid] == nums[right],此时不能判断mid是在最小值的左侧还是右侧,但是无论right是否是最小值,都有至少mid相同值替代,因此可以忽略right
--right;
}
return nums[left];
}
};
8. Leetcode-278 第一个错误的版本
题目
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion (version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例 1:
输入:n = 5, bad = 4
输出:4
解释:
调用 isBadVersion (3) -> false
调用 isBadVersion (5) -> true
调用 isBadVersion (4) -> true
所以,4 是第一个错误的版本。
示例 2:
输入:n = 1, bad = 1
输出:1
题解思路
第一个错误的版本其实就是第一个调用 isBadVersion ()结果为 true 的版本。
题解
class Solution {
public:
int firstBadVersion(int n)
{
int left = 1, right = n;
while(left < right)
{
int mid = left + (right - left) / 2;
if(isBadVersion(mid))
right = mid;
else
left = mid + 1;
}
if(isBadVersion(left)) return left; // 后处理
return -1;
}
};
9. Leetcode-162 寻找峰值
题目
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
你必须实现时间复杂度为 O (log n) 的算法来解决此问题。
示例 1:
输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5,其峰值元素为 6。
题解思路
爬坡法,数组的峰值有这样一个特点,峰值的左侧一定是一个上坡,右侧必然是一个下坡,因此可以根据这个特点来二分查找数组中的峰值:
定义峰值在区间[left, right]
中,left 和 right 初始化为数组边界,mid 为区间中间索引;若 nums[mid] < nums[mid + 1]
,即此时mid的右侧是一个上坡,那么必然有一个峰值出现在右区间,并且该峰值不会是nums[mid + 1],此时忽略左区间,直接查找右区间, left = mid + 1
;若nums[mid] >= nums[mid + 1]
,即此时 mid 的右侧处于下坡,那么必然有一个峰值出现在左区间,mid 可能是峰值,此时忽略右区间,直接查找左区间,right = mid
;
题解
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])
// 正在上坡,说明必然有一个峰值在mid右侧,因此忽略左区间,向右区间查找;
left = mid + 1;
else
// nums[mid] <= nums[mid + 1],即正在下坡,说明必然有一个峰值在mid左侧,mid可能是峰值,因此忽略右区间,向右区间查找;
right = mid;
}
return left;
}
};
10. Leetcode-33 搜索旋转排序数组
题目
整数数组 nums 按升序排列,数组中的值
互不相同
。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums. Length)上进行了旋转,
使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标从 0 开始计数)。
例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你旋转后的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O (log n) 的算法解决此问题。
示例 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
题解思路
将旋转数组一分为二后,两部分中一定有一部分是有序的!根据分割后的两个数组的有序情况,再进行二分查找即可:
- 若[left, mid]部分是有序的,并且 target 的大小满足 nums[left] <= target < nums[mid],则在左半区查找, right = mid - 1; 否则,就在[mid + 1, right]中找, left = mid + 1;
- 若[mid, right]部分是有序的,并且 target 的大小满足 nums[mid] < target <= nums[right],则在右半区查找, left = mid + 1; 否则,就在[left, mid]中找, right = mid - 1;
题解
class Solution1
{
int search(vector<int>& nums, int target)
{
int left = 0, right = nums.size() - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (nums[mid] == target) return mid;
if (nums[left] <= nums[mid]) //[left, mid)是有序区间
{
if(nums[left] <= target && target < nums[mid])
//target在区间[left, mid)中
right = mid - 1;
else
left = mid + 1;
}
else // (mid, right] 是有序区间
{
if(nums[mid] < target && target <= nums[right])
//target在区间(mid, right]中
left = mid + 1;
else
right = mid - 1;
}
}
return -1;
}
};
11. Leetcode-33 搜索旋转排序数组 II
题目
已知存在一个按非降序排列的整数数组 nums ,数组中的值
不必互不相同
。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums. Length)上进行了旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标从 0 开始计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。
给你旋转后的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 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[left] == nums[mid] == nums[right]
的情况,此时我们无法判断二分后哪部分数组是有序的,此时只能 left++,right--
,然后再在新区间上继续二分查找。
题解
class Solution
{
public:
bool 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) return true;
if(nums[left] == nums[mid] && nums[mid] == nums[right])
{
left++;
right--;
}
else if(nums[left] <= nums[mid])
{
if(nums[left] <= target && target < nums[mid])
right = mid - 1;
else
left = mid + 1;
}
else
{
if(nums[mid] < target && target <= nums[right])
left = mid + 1;
else
right = mid - 1;
}
}
return false;
}
};
12. Leetcode-658 找到 K 个最接近的元素
题目
给定一个排序好的数组 arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
|a - x| < |b - x| 或者
|a - x| == |b - x| 且 a < b
示例 1:
输入:arr = [1,2,3,4,5], k = 4, x = 3
输出:[1,2,3,4]
示例 2:
输入:arr = [1,2,3,4,5], k = 4, x = -1
输出:[1,2,3,4]
题解思路
由于数组是排好序的,因此可以先使用二分查找,查找第一个 >= x 的元素的位置
, 设其为 right,right 的前一个位置是 left,则数组被分为了两部分—— arr[0] 到 arr[left]的元素都是小于 x 的,arr[right]到数组末尾的元素都是大于 x 的
,right 和 left 处的元素都是最接近 x 的。
此时再使用双指针,寻找 left 与 right 指针附近与 x 相差最小的 k 个元素,比较 left 与 right 指向的元素与 x 的差值,若 x - arr[left] <= arr[right] - x
,则 left--
,否则 right++
,最后区间 [left + 1, right - 1]
中的元素即为所求。
题解
class Solution
{
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x)
{
int left = 0, right = arr.size() - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (arr[mid] >= x)
right = mid;
else
left = mid + 1;
}
left = right - 1;
while(k--)
{
if(left < 0) // right = 0
right++;
else if(right >= arr.size()) // right = arr.size() - 1
left--;
else if(x - arr[left] <= arr[right] - x) // 左指针left处的元素更接近x
left--;
else // 右指针right处的元素更接近x
right++;
}
vector<int> res(arr.begin() + left + 1, arr.begin() + right);
return res;
}
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏