不同但是相似的二分查找 题集
Leetcode 704 二分查找
力扣(LeetCode)题目跳转链接
题目描述:
给定一个 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
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
问题分析:
!值得注意的是 数组是升序的!
首先取数组中间元素的位置,不难写出int mid = (left + right) / 2;,这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在二分法 (opens new window)中尤其需要注意!
所以可以这么写:int mid = left + ((right - left) / 2);
需要有这个意识!
官方题解
// 版本一 target 是在一个在左闭右闭的区间里,也就是[left, right]
class Solution {public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right)
{ // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2); // 防止溢出 等同于(left + right)/2
if (nums[middle] > target)
{ right = middle - 1; // target 在左区间,所以[left, iddle - 1]
}
else if (nums[middle] < target)
{ left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}};
当需要在一个有序数组中查找一个特定值时,二分查找算法是一种高效的解决方案。它通过将目标值与数组中间元素进行比较来确定目标值所在的区间,并将搜索范围缩小到数组的一半,以便在更短的时间内找到目标值。 这个算法的实现包含三个主要步骤:
1.初始化搜索范围:将搜索范围定义为整个数组,即[left, right]。在本例中,left被初始化为0,right被初始化为nums数组的最后一个索引。
2.不断缩小搜索范围:在while循环中,通过比较中间元素与目标值的大小,可以确定目标值所在的区间。如果中间元素比目标值大,则目标值必定在左侧区间,因此将搜索范围缩小到左侧区间;如果中间元素比目标值小,则目标值必定在右侧区间,因此将搜索范围缩小到右侧区间。如果中间元素等于目标值,则找到了目标值,直接返回该元素的下标。
3.搜索完成:如果while循环完成时仍未找到目标值,则表示目标值不在数组中,返回-1。
4.需要注意的是,在计算中间元素时,要使用(left + (right - left) / 2)而不是(left + right) / 2,以防止整数溢出的问题。另外,循环条件中使用了left <= right而不是left < right,是因为当搜索范围缩小到只剩下一个元素时,该元素仍可能是目标值,因此需要将搜索范围限定为[left, left]或[right, right]。
描述一下其中left right充当的角色和功能
在二分查找算法中,left和right变量用来表示当前搜索范围的左右边界。具体来说,它们的功能如下:
left表示当前搜索范围的左边界,初始值为0,也就是数组的第一个元素的下标。
right表示当前搜索范围的右边界,初始值为nums.size() - 1,也就是数组的最后一个元素的下标。
在算法执行过程中,left和right的值会根据当前中间元素与目标值的大小关系,不断缩小搜索范围,以便在更短的时间内找到目标值。
当left和right相等时,搜索范围缩小到了只有一个元素,此时需要根据这个元素是否等于目标值来决定是否找到目标值。
总之,left和right的角色是表示当前搜索范围的左右边界,它们的值会随着搜索过程的进行而不断变化,以缩小搜索范围。在搜索过程结束后,left和right所表示的搜索范围可能变成了只有一个元素,此时需要根据这个元素是否等于目标值来确定算法的返回值。
牛客网 实现有重复数字的有序数组的二分查找。
算法思路:
由于题目要求输出第一个大于等于查找值的位置,因此需要对二分查找的结果进行判断。当查找到数组中某个位置的值等于查找值时,需要继续向前查找,直到找到第一个大于等于查找值的位置;当未查找到查找值时,需要输出下标+1,即为数组长度加1。
具体实现:
定义左右指针left和right,初始值分别为0和n-1,表示在整个数组范围内进行查找。
在循环中,每次计算中间位置mid,如果中间位置的值等于查找值,需要向前查找,直到找到第一个大于等于查找值的位置;如果中间位置的值小于查找值,需要将左指针left更新为mid+1,继续在右半部分查找;如果中间位置的值大于查找值,需要将右指针right更新为mid-1,继续在左半部分查找。
如果循环结束时仍未查找到查找值,则输出right+1,即为数组长度加1。
时间复杂度:
由于每次将查找范围缩小一半,因此时间复杂度为O(logn)。
#include <iostream>
#include <vector>
using namespace std;
int binarySearch(vector<int> &nums,int target)
{
int left=0;
int right=nums.size()-1;
while(left<=right)
{
int middle=left+(right-left)/2;
if(target<=nums[middle])
{
right=middle-1;
}
else
{
left=middle+1;
}
}
return left<nums.size()?left+1:nums.size()+1;
}
int main()
{
int len,target;
scanf("%d",&len);
scanf("%d",&target);
vector<int> nums(len);
for(int i=0;i<len;i++)
{scanf("%d",&nums[i]);
}
cout<<binarySearch(nums,target)<<endl;
return 0;
}
在该代码中,首先使用scanf输入n和v,然后使用for循环和scanf输入整个数组nums。在二分查找函数binarySearch中,根据题目要求,使用左闭右开区间进行二分查找。如果target小于等于nums[middle],则将右指针right更新为middle-1;否则将左指针left更新为middle+1。最后,返回left+1或n+1,即为所求的结果。
Q: return left < nums.size() ? left + 1 : nums.size() + 1; 这里为什么是left+1
A:在该代码中,二分查找的目标是在数组中查找第一个大于等于查找值的位置,因此当找到一个位置,该位置的值大于等于查找值时,需要返回该位置的下标加1。这是因为题目要求的是位置而不是下标,下标是从0开始的,因此需要将下标加1才能得到对应的位置。如果left到达了数组的末尾,即left == nums.size(),则说明数组中没有大于等于查找值的数,需要返回nums.size()+1。因此,最后的返回结果可以使用三目运算符进行判断和选择。
QR1 二分查找
问题描述:
对于一个有序数组,我们通常采用二分查找的方式来定位某一元素,请编写二分查找的算法,在数组中查找指定元素。
给定一个整数数组A及它的大小n,同时给定要查找的元素val,请返回它在数组中的位置(从0开始),若不存在该元素,返回-1。若该元素出现多次,请返回第一次出现的位置。
测试样例:
[1,3,5,7,9],5,3
返回:1
class BinarySearch {
public:
int getPos(vector<int> A, int n, int val)
{
if (n==0||A.size()==0)
{
return -1;
}
int left=0;
int right=A.size()-1;
while (left<=right)
{
int middle=left+(right-left)/2;
if(val<A[middle])
{
right=middle-1;
}
else if(val>A[middle])
{
left=middle+1;
}
else //相等
{
int index=middle;
while (index--)
{
if (val!=A[index])
{
return index+1;
}
}
}
}
return -1;
}
};
Q:我这样写的 哪里出错了?
A:在这个算法实现中,使用了 index 来查找第一次出现的位置。但是它的逻辑有误,因为在最后一次循环时,index 的值将被减到 -1,因此返回的位置将是 -1,而不是数组的第一个位置。
else { // 相等
int index = middle;
while (index >= 0 && A[index] == val) {
index--;
}
return index + 1;
}
Q:是否有必要使用index,我看别人写的题解没用index,直接用的middle,有什么优劣嘛?
A:应该使用 middle 作为起点进行查找第一次出现的位置,因为 middle 已经指向了一个等于目标值的元素。可以向左移动 middle 直到找到第一个不等于目标值的元素的位置。
else { // 相等
while (middle >= 0 && A[middle] == val) {
middle--;
}
return middle + 1;
}
以上两个算法的时间复杂度都为 O(log n),但是后者更简洁并且更加高效。
Leetcode 35 搜索插入位置
题目跳转链接
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
题目中说了:nums 为 无重复元素 的 升序 排列数组
请必须使用时间复杂度为 O(log n) 的算法。
class Solution {
public:
int searchInsert(vector<int>& nums, int target)
{ //题目已经说明了 Nums为升序
if(nums.size()==0) return 0;
int left=0;
int right=nums.size()-1;
if(target<nums[0]) {return 0;}
while(left<=right)
{
int middle=left+(right-left)/2;
if(target<nums[middle])
{ right=middle-1;
}
else if(target>nums[middle])
{
left=middle+1;
}
else
{
return middle;
}
}
return right+1;
}
};
Q:如果题目没有说明 “nums 为 无重复元素 的 升序 排列数组” 应该怎么写
A: 如果题目没有说明 "nums 为 无重复元素 的 升序 排列数组",则需要假设 nums 数组中可能存在重复元素,并且需要进行特判。此时,可以使用二分查找算法,找到第一个大于等于目标值的元素的索引,即为插入位置。
(其实这里,它表示的意思是 nums还是升序的,但是有重复元素的情况)
具体实现如下:
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 - 1;
}
}
return left;
}
};
时间复杂度为 O(log n)。
Q: nums是降序就不能使用二分查找了嘛?
A:如果 nums 是降序排列的,我们仍然可以使用二分查找算法来找到目标值或其插入位置。在使用二分查找算法时,我们只需要将搜索区间的左右边界取反即可。具体来说,每次二分查找时,我们可以将目标值与中间值比较,如果目标值大于中间值,则说明目标值应该在左边的区间中查找,否则在右边的区间中查找,这与在升序排列数组中查找时是相反的。
因此,如果 nums 是降序排列的,我们仍然可以使用二分查找算法来找到目标值或其插入位置,时间复杂度仍为 O(log n)。