leetcode 经典二分查找算法题目(思路、方法、code)

leetcode 经典二分查找算法题目(思路、方法、code)

文章目录

      • leetcode 经典二分查找算法题目(思路、方法、code)
        • [35. 搜索插入位置](https://leetcode-cn.com/problems/search-insert-position/)
        • [69. x 的平方根](https://leetcode-cn.com/problems/sqrtx/)
        • [34. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)
        • [154. 寻找旋转排序数组中的最小值 II](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/)
        • [33. 搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/)

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 1:
输入: [1,3,5,6], 5
输出: 2
    
示例 2:
输入: [1,3,5,6], 2
输出: 1

分析:由于数组是一个有序数组,故可以采用二分搜索找位置,可以在 O ( l o g n ) O(logn) O(logn) 的时间复杂度内找到。

  • 若target在数组中出现,则典型的二分查找方法即可
  • 若target在数组中没有出现
    • 若targetnums[mid-1] ,说明返回的下标应该是 mid
    • 若target>nums[mid] && target
    • 若mid==0 || mid==size-1 ,也就是说target应该的位置在于数组外
      • 若target
      • 若target>nums[size-1],返回size
//暴力的方法:
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) 
    {
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]>=target)
                return i;
        }
        return nums.size();
    }
};
//二分查找的方法:
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) 
    {
       int index=-1;
       int begin=0,end=nums.size()-1;
       while(index==-1) //未找到位置
       {
           int mid=(begin+end)/2;
           if(target==nums[mid])
                index=mid;
           else if(target<nums[mid])  //mid处值大于target
           {
               if(mid==0||target>nums[mid-1]) //短路,不用判断mid-1非法
                    index=mid;   //确定了target的位置
               end=mid-1; //因为已经判断过mid的位置,直接mid-1即可
           }
           else if(target>nums[mid])
           {
               if(mid==nums.size()-1||target<nums[mid+1])
                        index=mid+1;
                begin=mid+1;
           }
       }
       return index;
    }
};

69. x 的平方根

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例 1:
输入: 4
输出: 2

示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842..., 
     由于返回类型是整数,小数部分将被舍去。

分析:x的平方根,在此处是指最大的满足 r*r<=x的r值,故可以用二分查找,因为该值一定在0~x间,故可以逐步判断每次减少一半区间,在 O ( l o g n ) O(logn) O(logn) 的时间复杂度内找到其应该在的位置

class Solution {
public:
    int mySqrt(int x) 
    {
        int left = 0, right = x, ans = -1;
        while (left <= right) 
        {
            int mid = (left+right)/ 2;
            if ((long long)mid * mid <= x)  //避免溢出
            {
                ans = mid;
                left = mid + 1;
            }
            else 
            {
                right = mid - 1;
            }
        }
        return ans;
    }
};

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

分析:实际上,要找的位置,可以这样理解:如果target没有出现,则返回[-1,-1],否则,返回target的最左索引和target的最右索引。由于数组是排序的,因此采用二分查找,思路如下:

  • 首先利用二分查找最左索引,最左索引的特征是该坐标左侧应当不是该值,该坐标处是该值
  • 然后同样方法查找最右索引,最右索引的特征是该坐标右侧不是该值,该坐标处是该值
  • 返回即可
class Solution {
public:
    int left_bound(vector<int> & nums,int target)
    {
        int begin=0,end=nums.size()-1;
        while(begin<=end)
        {
            int mid=(begin+end)/2;
            if(target==nums[mid]) //相等时候还要找到最左侧的该值
            {
                if(mid==0||nums[mid-1]<target) //说明是最左的索引了
                    return mid;
                end=mid-1; //否则令end左移
            }
            else if(target<nums[mid])
                end=mid-1;
            else if(target>nums[mid])
                begin=mid+1;
        }
        return -1;
    }
    int right_bound(vector<int> & nums,int target)
    {
        int begin=0,end=nums.size()-1;
        while(begin<=end)
        {
            int mid=(begin+end)/2;
            if(target==nums[mid]) //相等时候还要找到最右侧的该值
            {
                if(mid==nums.size()-1||nums[mid+1]>target) //说明是最右的索引了
                    return mid;
                begin=mid+1; //否则令end左移
            }
            else if(target<nums[mid])
                end=mid-1;
            else if(target>nums[mid])
                begin=mid+1;
        }
        return -1;
    }

    vector<int> searchRange(vector<int>& nums, int target) 
    {
        vector<int> result;
        int left=left_bound(nums,target);
        int right=0;
        if(left==-1)
            right=-1;
        else
            right=right_bound(nums,target);
        result.push_back(left);
        result.push_back(right);
        return result;

    }
};

154. 寻找旋转排序数组中的最小值 II

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

注意数组中可能存在重复的元素。

示例 1:
输入: [1,3,5]
输出: 1
示例 2:
输入: [2,2,2,0,1]
输出: 0

分析:原始数组为一个非递减排序,因此,我们旋转后,可知该数组应当是两个非递减序列组成,并且右侧序列的最右端(即最大值)一定不大于左侧序列的最左端(即最小值)

可以举一个具体例子来分析

给定数组  1,2,3,4,5
旋转后可以为  3,4,5,1,2
因此可以看出   3,4,5    1,2 分别是非递减序列

因此根据该性质,易知可用二分查找

  • 找到区域边界两个位置 i i i j j j
  • 如果 左<右 ,很显然数组没有发生旋转,否则一定是左<=右
  • 找到中间位置 m i d mid mid
    • 如果中间位置的值小于右侧,说明最小值在右侧,令 i = m i d + 1 i=mid+1 i=mid+1 ,返回最初循环
    • 如果中间位置的值大于右侧,说明最小值在左侧,令 j = m i d − 1 j=mid-1 j=mid1 ,返回最初循环
    • 如果中间位置的值等于右侧,则没办法确定最小值的位置,此时令 i i i++ 继续推进循环即可
  • 直至i=j,即找到最小值
class Solution {
public:
    int findMin(vector<int>& nums) 
    {
        int size=nums.size();
        int i=0,j=size-1;
        while(i<j)
        {
   		    if(nums[i]<nums[j]) //说明数组没有旋转 
		         return nums[i];
		    int mid=(i+j)>>1;
		    if(nums[mid]>nums[j])
		    {
			    i=mid+1;continue;  //注意这里i=mid+1 
		    }
		    if(nums[mid]<nums[j])	
		    {
			    j=mid;           //j=mid  主要是让i和j必须保持相近,避免二者相邻时出现死循环 
			    continue;
		    }
		    if(nums[mid]==nums[j])//两个值相等的话没办法二分,只能逐步右移
		    {
			    i++;
		    }
        }
        return nums[i];
    }
};

33. 搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -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

分析:为了达到 O ( l o g n ) O(logn) O(logn) 的时间复杂度,因此需要采用二分查找。该题采用二分查找时需要注意的是,每次找到中点后,不应该直接去比较中点与target的值,而应该首先确定左右两个区间是否是完全递增的。完全递增的区间和两个分别递增的区间的判断方式不一样。

  • 每次找到中点位置
  • 如果target等于中点处的值,返回即可
  • 如果target小于中点处的值
    • 如果左侧是递增区间
      • target>=nums[begin] ,则在[begin,mid-1]中查找
      • 否则在[mid+1,end]中查找
    • 如果左侧是旋转区间
      • 因为target小于中点处的值,故一定在旋转区间,在[begin,mid-1]中查找
  • 如果target大于中点处的值
    • 如果左侧是递增区间,
      • 则直接在[mid+1,end]中查找
    • 如果左侧是旋转区间
      • 如果target>=nums[begin],则在[begin,mid-1]中查找
      • 否则在[mid+1,end]中查找
class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int begin=0,end=nums.size()-1;
        while(begin<=end)
        {
            int mid=(begin+end)/2;
            if(target==nums[mid])
                return mid;
            else if(target<nums[mid])
            {
                if(nums[begin]<nums[mid])//左侧递增
                {
                    if(target>=nums[begin])
                        end=mid-1;
                    else
                        begin=mid+1;
                }
                else if(nums[begin]>nums[mid])//左侧旋转
                {
                    end=mid-1;
                }
                else if(nums[begin]==nums[mid]) //只剩下两个元素
                {
                    begin=mid+1;
                }
            }
            else if(target>nums[mid])
            {
                if(nums[begin]<nums[mid])//左侧递增
                {
                    begin=mid+1;
                }
                else if(nums[begin]>nums[mid]) //左侧旋转
                {
                    if(target>=nums[begin])
                        end=mid-1;
                    else
                        begin=mid+1;
                }
                else if(nums[begin]==nums[mid]) //只剩两个元素
                    begin=mid+1;
            }
        }
        return -1;    
    }
};

你可能感兴趣的:(数据结构与算法)