不同但是相似的二分查找 题集

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)。

posted @ 2023-03-19 01:18  专心Coding的程侠  阅读(16)  评论(0编辑  收藏  举报