二分查找了解一下?(二分训练leetcode附题解)

这篇笔记,将给大家介绍二分查找的各种常见形式,并且附有leetcode题解,一起来学习二分查找把!

二分查找的原理

二分查找就是在一个给定的有序区间内找一个值,每次找中间值,然后舍弃一半的区间,这样每次查找就可以减少一半的数据,时间复杂度是O(logN).
二分查找可以应用于数组,是因为数组具有有随机访问的特点,并且数组是有序的。二分查找体现的数学思想是「减而治之」,可以通过当前看到的中间元素的特点推测它两侧元素的性质,以达到缩减问题规模的效果
了解完原理来看一下第一题。

704.二分查找

这是最简单的二分查找,思路就是上面所说的。

int search(int* nums, int numsSize, int target){
    int left = 0;
    int right = numsSize-1;
    while(left<=right)
    {
        int mid = left+(right-left)/2;
        if(nums[mid]==target)
        {
            return mid;
        }
        if(nums[mid]<target)
        {
            left = mid+1;
        }
        else
        {
            right = mid-1;
        }
    }
    return -1;
}

374. 猜数字大小

思路很简单就是通过二分查找找一个1—n,之间的数字,要注意mid如果直接用(left+right)/2会溢出,所以我们可以巧用减法。left+(right-left)/2。就好了。

int guessNumber(int n){
	int left = 1;
    int right = n;
    while(left<=right)
    {
        int mid = left+(right-left)/2;//防止溢出
        if(guess(mid)==0)//猜对了
        {
            return mid;
        }
        else if(guess(mid)==1)//猜小了
        {
            left = mid+1;
        }
        else
        {
            right = mid-1;
        }
    }
    return -1;
}

35. 搜索插入位置

该题的思路就是,在这个数组里面找target,找到了还是返回下标,找不到就返回插入位置的下标,也就是我们要找的这个下标对应的值一定是>=target的,并且还要接近target,所以也就是寻找大于等于target的最小下标,所以一开始定义答案下标为numsize,也就是数组所有元素都小于target的时候,元素就该在数组尾部插入。

int searchInsert(int* nums, int numsSize, int target){
    int left = 0;
    int right = numsSize-1;
    int ans = numsSize;
    while(left<=right)
    {
        int mid = (left+right)>>1;
        if(nums[mid]>=target)
        {
            ans = mid;
            right = mid-1;
        }
        else
        {
            left = mid+1;
        }
    }
    return ans;
}

278. 第一个错误的版本

思路:就是一组数字中,后半部分都是错的(或者说都是一样的数字)找到这个错误出现的第一个位置,可以转换成,求>=target的最小下标。先二分如果找到了error则向左缩小区间,直到找到那个最小的target的下标值。

int firstBadVersion(int n) {
    int left = 1;
    int right = n;
    int ans = 0;
    while(left<=right)
    {
        int mid = left+(right-left)/2;
        if(isBadVersion(mid))
        {
            ans = mid;
            right = mid-1;
        }
        else
        {
            left = mid+1;
        }
    }
    return ans;
}

69. x 的平方根

思路1:暴力循环
思路2:二分思想,就是我们要找y使得y * y <=x,因为返回的是整数舍弃了小数,所以我们就要找的这个y就是<=x的最大值。先定义一个较大的区间,然后找到<=x的值保存,不断逼近x即可。

int mySqrt(int x){
    int left = 0;
    int right = 100000;
    int y = 0;
    while(left<=right)
    {
        long long mid = (left+right)>>1;
        if((mid*mid)<=x)
        {
            y = mid;
            left = mid+1;
        }
        else
        {
            right = mid-1;
        }
    }
    return y;
}

367. 有效的完全平方数

方法1:暴力遍历,从1开始遍历,直到sqr大于num则返回false,若是其中有sqr==num则返回true

bool isPerfectSquare(int num){
    long long int i = 1;

    while(1)
    {
        long long  int sqr = i*i;
        i++;
        if(sqr==num)
        {
            return true;
        }
        if(sqr>num)
        {
            return false;
        }

    }
}

方法2:使用二分查找,因为满足条件的值,假设为mid(与代码中保持一致),mid * mid一定是这个范围 1<= mid * mid <=num,所以查找的边界也就固定了,二分查找直接梭哈就可以了。如果能找到就返回true,找不到也就是循环结束,就返回false。

bool isPerfectSquare(int num){
    long long  left = 1;
    long long right = num;
    
    while(left<=right)
    {
        long long mid = (left+right)>>1;
        if(mid*mid == num)
        return true;
        if(mid*mid<num)
        {
            left = mid+1;
        }
        else
        {
            right = mid-1;
        }
    }
    return false;
}

441. 排列硬币

思路:假设有x行完整,那么1+2+3+…+x<=n,用等差数列求和即可得到x*(1+x)/2<=n。用二分查找寻找这个x即可,也就是使得该表达式值小于等于n的最大值。
注意防止溢出,要使用long long类型,最后返回的时候在强转回int

int arrangeCoins(int n){
    long long left = 0;
    long long right = n;
    long long ans = 0;
    while(left<=right)
    {
        long long x = (left+right)>>1;
        if(x*(x+1)/2<=n)
        {
            ans = x;
            left = x+1;
        }
        else
        {
            right = x-1;
        }
    }
    return (int)ans;
}

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

思路:首先有序数组中有一组target区间,或者没有,假设有,找到>=target的最小下标,即为开始位置,找到>=target+1的下标然后再-1,就是结束位置。然后运用前面的知识,写一个求>=target的最小下标的函数,即可,对返回值进行判断,如果不合理,就是数组内没有target,直接两个数都是-1.如果符合,就是代表,数组中target存在,那么再求出>=target+1的最小下标再-1,就是结束位置。

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */

int Binary_find(int* nums,int numsSize,int target)
{
    int left = 0;
    int right = numsSize-1;
    int ans = numsSize;
    while(left<=right)
    {
        int mid = (left+right)>>1;
        if(nums[mid]>=target)
        {
            ans = mid;
            right = mid-1;
        }
        else
        {
            left = mid+1;
        }
    }
    return ans;
}


int* searchRange(int* nums, int numsSize, int target, int* returnSize){
    int* ans = (int*)malloc(sizeof(int)*2);
    *returnSize = 2;
    int left = 0 ;
    int right = numsSize-1;
    //求>=target的最小下标
    int ret = Binary_find(nums,numsSize,target);
    if(ret!numsSize && nums[ret]==target)//通过返回值判断数组中有没有target
    //ret!numsSize(这一句判断如果数组为空数组,那么二分的循环就进不去就会返回numsize给ret,防止num[ret]越界)
    {
        ans[0] = ret;
        ans[1] = Binary_find(nums,numsSize,target+1)-1;//找到了>=target+1的最小下标,
        //那么下标-1,就是target的最大下标。
    }
    else
    {
        ans[1] = ans[0] = -1;
    }
    return ans;
}

349. 两个数组的交集

思路:先将数组排序,然后遍历第一个数组,将每个元素到nums2中进行二分查找,如果找到了就放入新数组,因为交集不能有重复的,所以定义一个pre = -1,pre用来存储当前交集元素的前一个元素,如果后一个交集元素与pre相同,直接跳出二分进行下一次二分,如果后一个交集元素与pre不相同,则放入数组并更新pre。

int cmp(const void* str1,const void* str2)
{
    return *(int*)str1 - *(int*)str2;
}

int* intersection(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize){
    qsort(nums1,nums1Size,sizeof(int),cmp);
    qsort(nums2,nums2Size,sizeof(int),cmp);
    *returnSize = 0;
    int* ans = (int*)malloc(sizeof(int)*nums1Size);
    int pre = -1;

    for(int i =0;i<nums1Size;i++)
    {
        int target = nums1[i];
        int left = 0;
        int right = nums2Size-1;
        while(left<=right)
        {
            int mid = left+(right-left)/2;
            if(nums2[mid]==target)
            {
                if(pre != target)
                {
                ans[*returnSize] = target;
                (*returnSize)++;
                pre = target;
                break;
                }

            }
            if(nums2[mid]<target)
            {
                left = mid+1;
            }
            else
            {
                right = mid-1;
            }
        }
    }
    return ans;
}

744. 寻找比目标字母大的最小字母

思路还是二分思想,目标字母是大于等于target的那个最小的字母,也就是再数组中找到一个大于等于target的最小的元素
特殊情况:题中规定,如果数组最大的元素都比target小,那就返回数组第一个元素。

char nextGreatestLetter(char* letters, int lettersSize, char target){
    int left = 0;
    int right = lettersSize-1;
    char ans = letters[lettersSize-1];
    while(left<=right)
    {
        int mid = left+(right-left)/2;
        if(letters[mid] > target)
        {
            ans = letters[mid];
            right = mid-1;
        }
        else
        {
            left = mid+1;
        }
    }
    return ans>target?ans:letters[0];//注意字母是循环出现的,如果最大的字符都没有比target大,那就返回第一个字母
}

852. 山脉数组的峰顶索引

思路:为了找最大值,山峰的左边是递增的,山峰的右边是递减的,找到山峰则直接返回,如果mid+1大于了mid则说明此时mid位于左山坡,所以left = mid+1;反之则在右山坡,right = mid-1;

int peakIndexInMountainArray(int* arr, int arrSize){
    int left = 0;
    int right = arrSize-1;
    int mid = 0;
    while(left<=right)
    {
        mid = (left+right)>>1;
        if(arr[mid]>arr[mid+1] && arr[mid]>arr[mid-1])
        {
            return mid;
        }
        if(arr[mid]<arr[mid+1])
        {
            left = mid+1;
        }
        else
        {
            right = mid-1;
        }
    }
    return mid;
}

总结

相信做完这些练习,你一定对二分有了更深的理解,其实二分不一定要在有序数组内,比如最后的山峰问题,但是一定是区间有序的,根据中间值你可以判断舍弃掉那一部分区间,这才是二分的思想。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KissKernel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值