牛客101:二分查找/排序

目录

一、二分查找

二、二维数组中的查找

三*、寻找峰值

四、数组中的逆序对

五**、旋转数组的最小数字

六、比较版本号


有时数据排序后很方便二分,所以这也是为啥官方题解名字会加个/排序吧,= ̄ω ̄=

一、二分查找

二分查找-I_牛客题霸_牛客网

注意while循环条件含有等于,因为我们还需要判断元素的相等,当left==right我们还需要对这个位置的元素做一步判断,所以得是等于。

如果是要找相对位置不需要进一步判断元素相等的情况,则不需要加上等于,因为当left==right时,该位置就是我们需要的位置。

    int search(vector<int>& nums, int target) {
        //特殊处理
        int n=nums.size();
        if(n==0)return -1;
        int left=0,right=n-1;
        //朴素二分等于时仍然需要判断
        while(left<=right)
        {
            int mid=(left+right)>>1;
            //不等时跳过mid
            if(nums[mid]<target)left=mid+1;
            else if(nums[mid]>target)right=mid-1;
            else return mid;
        }
        return -1;
    }

二、二维数组中的查找

二维数组中的查找_牛客题霸_牛客网

解法一:二维遍历+每一层二分查找

二维遍历嵌套朴素二分模板即可,当tartget小于遍历到的一维数组第一个元素,直接return false

    bool Find(int target, vector<vector<int> >& array) {
        int m=array.size(),n=array[0].size();
        if(m==0||n==0)return false;
        
        for(int i=0;i<m;++i)
        {
            if(target<array[i][0])break;
            int left=0,right=n-1;
            while(left<=right)
            {
                int mid=(left+right)>>1;
                if(array[i][mid]<target)left=mid+1;
                else if(array[i][mid]>target)right=mid-1;
                else return true;
            }
        }
        return false;
    }

解法二:线性查找

摘自牛客第一篇题解,不得不说真是妙妙妙妙

    bool Find(int target, vector<vector<int> >& array) {
        int m=array.size(),n=array[0].size();
        if(m==0||n==0)return false;

        //每次只会往上或者往右
        int i=m-1,j=0;
        while(i>=0&&j<=n-1)
        {
            if(array[i][j]>target)--i;
            else if(array[i][j]<target)++j;
            else return true;
        }
        return false;
    }

三*、寻找峰值

寻找峰值_牛客题霸_牛客网

与该题的第三次会面

都说二分查找本质是利用数据的二段性,将数据划分为两个区间,然后舍弃一部分区间,高效地找到目标点。这里的二段性,就是数组元素的递增性和递减性

以下有四种正确策略,你可以按照你想写的那种,参考一下鄙人拙见。(*^-^*)

写到这里,思考是用nums[mid+1]还是nums[mid-1]参与判断

如果是mid+1:mid和mid+1之间,

要么写[mid]<[mid+1]:

mid+1是我要的,判断达成后写left=mid+1(为啥呢,这保证left为起点的左边保证都是有递增的,left起点右边仍然值得探索,如果right来得到赋值,那么右边的区域就无法探索了)

else right=mid;

因为这里是right赋值mid,所以mid应该采用选左边所以是left+right,这符合,按这个思路继续写

能过

    int findPeakElement(vector<int>& nums) {
        int n=nums.size();
        int left=0,right=n-1;
        while(left<right)
        {
            int mid=(left+right)>>1;
            if(nums[mid]<nums[mid+1])
            {
                left=mid+1;
            }
            else right=mid;
        }
        return left;
    }

要么写[mid]>[mid+1]:

mid是我要的,判断达成后left=mid,有误,应该是right=mid这里递减了,right为起点保证右边存在递减区间,左边的区间仍然值得探索

else mid+1就是我left要的,left=mid+1

因为mid赋值给了right,所以mid的策略选择采用左边,left+right嗯正确的,按照这个思路继续写

通过

    int findPeakElement(vector<int>& nums) {
        int n=nums.size();
        int left=0,right=n-1;
        while(left<right)
        {
            int mid=(left+right)>>1;
            if(nums[mid]>nums[mid+1])
            {
                right=mid;
            }
            else left=mid+1;
        }
        return left;
    }

如果是mid-1,[mid]和[mid-1]之间,

要么写[mid]<[mid-1]:

这样有点不符合顺序性,看着别扭,改一下

要么写[mid-1]<[mid]:

mid是我们要的,判断达成后,left赋值mid

else right=mid-1

因为left赋值mid,所以mid得选择右边的,mid修改成(left+right+1)>>,按照思路继续写代码

通过

    int findPeakElement(vector<int>& nums) {
        int n=nums.size();
        int left=0,right=n-1;
        while(left<right)
        {
            int mid=(left+right+1)>>1;
            if(nums[mid-1]<nums[mid])
            {
                left=mid;
            }
            else right=mid-1;
        }
        return left;
    }

要么写[mid-1]>[mid]:

思考一下这里显示出递减性,那么当然是mid-1赋值给right

else left=mid

left赋值mid,mid选择右边,left+right+1,嗯是正确的,继续写

    int findPeakElement(vector<int>& nums) {
        int n=nums.size();
        int left=0,right=n-1;
        while(left<right)
        {
            int mid=(left+right+1)>>1;
            if(nums[mid-1]>nums[mid])
            {
                right=mid-1;
            }
            else left=mid;
        }
        return left;
    }

四、数组中的逆序对

数组中的逆序对_牛客题霸_牛客网

法一:暴力

暴力解法,从左往右遍历设置两个指针,一个指针用来固定左边的数,然后另一个指针从该位置右边第一个位置开始左往右遍历,判断有几个比这个小的,然后让count加等于这个数。这样。一轮下来就能解决了。复杂度为N方。不过当然会超时的啦。

法二:归并排序

题目提示时间复杂度含有logn,要么是二分要么就是归并了

依照暴力的思路我们可以选择归并排序的思路,思路就是总体分为三步。将数组分为两块,然后。在左边那块先找出逆序对的数量,然后再找出右边的逆序对的数量,然后再将左右排序之后根据一左一右来找到逆序对。

首先我们不断分分分,在每一次归并的处理中,数据左边那一整块相对右边一整块的顺序和原数组是吻合的,我们可以先根据这个判断,同时在局部的时候排序,方便合之后,合之后,左边一整块和右边一整块的判断,比如看下面的两种策略

此外别忘了题目要求的取模,我的做法就是每次加法就取模

策略1:升序排序,合的时候,遇到右边数更小的情况,左边靠到mid的数据都可以,直接ret+=mid-cur1+1;

#include <vector>
class Solution {
    vector<int> tmp;
    const int MOD=1e9+7;
public:
    int InversePairs(vector<int>& nums) {
        tmp.resize(nums.size());
        return MergeSort(nums,0,nums.size()-1);
    }
    int MergeSort(vector<int>& nums,int left,int right)
    {
        if(left>=right)return 0;
        int mid=(left+right)>>1,ret=0;
        //分!
        ret=(ret+MergeSort(nums,left,mid))%MOD;
        ret=(ret+MergeSort(nums, mid+1, right))%MOD;
        //治!
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid&&cur2<=right)
        {
            //大于单独判断
            if(nums[cur1]<=nums[cur2])
            {
                tmp[i++]=nums[cur1++];
            }
            else 
            {
                ret=(ret+mid-cur1+1)%MOD;
                tmp[i++]=nums[cur2++];
            }
        }
        while(cur1<=mid)tmp[i++]=nums[cur1++];
        while(cur2<=right)tmp[i++]=nums[cur2++];
        //合!
        for(int i=left;i<=right;++i)
        {
            nums[i]=tmp[i-left];
        }
        return ret;
    }
};

策略2:降序,合的时候,找到左边数更大的情况,cur2靠到right都可以,直接ret+=right-cur2+1;

#include <vector>
class Solution {
    vector<int> tmp;
    const int MOD=1e9+7;
public:
    int InversePairs(vector<int>& nums) {
        tmp.resize(nums.size());
        return MergeSort(nums,0,nums.size()-1);
    }
    int MergeSort(vector<int>& nums,int left,int right)
    {
        if(left>=right)return 0;
        int mid=(left+right)>>1,ret=0;
        //分!
        ret=(ret+MergeSort(nums,left,mid))%MOD;
        ret=(ret+MergeSort(nums, mid+1, right))%MOD;
        //治!
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid&&cur2<=right)
        {
            //大于单独判断
            if(nums[cur1]<=nums[cur2])
            {
                tmp[i++]=nums[cur2++];
            }
            else 
            {
                ret=(ret+right-cur2+1)%MOD;
                tmp[i++]=nums[cur1++];
            }
        }
        while(cur1<=mid)tmp[i++]=nums[cur1++];
        while(cur2<=right)tmp[i++]=nums[cur2++];
        //合!
        for(int i=left;i<=right;++i)
        {
            nums[i]=tmp[i-left];
        }
        return ret;
    }
};

五**、旋转数组的最小数字

旋转数组的最小数字_牛客题霸_牛客网

题目要求logn那就二分了呗

该题怎么二段性?

数组现在旋转后变成了两段,两段彼此独立的单调递增,那么二段性可以根据数组最后一个元素判断,大于该元素的分为第一段,小于等于该元素的分到第二段(错误,后面纠正)

二段性搞清楚,我们的判断条件就明确了,可以直接写代码了

因为我们这里是找位置,所以while整体的判断条件不用写等于

最先开始是这么写的,没有考虑存在重复元素,如果有重复元素比如1,0,1,1,1,[mid]==nums[n-1]会赋值right=mid,这样右边的就全被跳过了

所以我们得严格要求,当[mid]<[n-1]的时候才更新right=mid,

所以真实情况是这样的

等于时我们需要特殊处理,因为答案是位于right为起点往左的一段区间的,我们只需让right--即可,让right逼近目标点

修改后还存在问题,我们应到舍弃right右边部分,将[right]替换[n-1]的位置,这才是最终正确代码

    int minNumberInRotateArray(vector<int>& nums) {
        int n=nums.size();
        int left=0,right=n-1;
        while(left<right)
        {
            int mid=(left+right)>>1;
            if(nums[mid]>nums[right])
            {
                left=mid+1;
            }
            else if(nums[mid]<nums[right])
            {
                right=mid;
            }
            else --right;
        }
        return nums[left];
    }

六、比较版本号

比较版本号_牛客题霸_牛客网

解法:双指针遍历

这题为啥放在二分,搞不懂

    int compare(string version1, string version2) {
        int n1=version1.size(),n2=version2.size();
        int cur1=0,cur2=0;
        while(cur1<n1||cur2<n2)
        {
            long num1=0;
            while(cur1<n1&&version1[cur1]!='.')
            {
                num1=num1*10+version1[cur1]-'0';
                ++cur1;
            }
            //跳过.
            ++cur1;

            long num2=0;
            while(cur2<n2&&version2[cur2]!='.')
            {
                num2=num2*10+version2[cur2]-'0';
                ++cur2;
            }
            ++cur2;
            
            if(num1>num2)return 1;
            if(num1<num2)return -1;
        }
        return 0;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_dindong

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

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

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

打赏作者

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

抵扣说明:

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

余额充值