一篇学会写出不会死循环的二分

在这里插入图片描述

不会死的二分模板

朋友们在写二分查找的题目的时候,写出的二分是不是经常死循环。

right的值是mid-1还是mid+1?还是就等于mid呢?

image-20220430191141660

重点来了,今天给大家介绍一种不会出现死循环的二分写法

寻找target下界模板

int findPosition(vector<int> &nums,int target)
{
    int left=0;
    int right=nums.size()-1;
    //结束条件为left和right相邻
    while(left+1<right)
    {
        int mid=left+(right-left)/2;
        if(a[mid]==target)
            right=mid;
        else if(a[mid]<target)
            left=mid;
        else if(a[mid>target])
            right=mid;
    }
    if(a[left]==target)
        return left;
    if(a[right]==target)
        return right;
    return -1;
}

寻找target上界模板

int findPosition(vector<int> &nums,int target)
{
    int left=0;
    int right=nums.size()-1;
    //结束条件为left和right相邻
    while(left+1<right)
    {
        int mid=left+(right-left)/2;
        if(a[mid]==target)
            left=mid;
        else if(a[mid]<target)
            left=mid;
        else if(a[mid>target])
            right=mid;
    }
    if(a[right]==target)
        return right;
    if(a[left]==target)
        return left;
    return -1;
}

题目

模板熟悉了,我们就一起来练习几个经典的二分题目

在这里插入图片描述

题目一:457 · 经典二分查找问题 - LintCode

在这里插入图片描述

最经典的二分查找的题目。

class Solution {
public:
    int findPosition(vector<int> &nums, int target) {
        // write your code here
        if(nums.size()==0)
            return -1;
        int left=0;
        int right=nums.size()-1;
        while(left+1<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]==target)
            {
                right=mid;
            }
            else if(nums[mid]<target)
            {
                left=mid;
            }
            else if(nums[mid]>target)
            {
                right=mid;
            }
        }
        if(nums[left]==target)
            return left;
        if(nums[right]==target)
            return right;
        return -1;
    }
};
题目二:458 · 目标最后位置 - LintCode)

image-20220430184129109

class Solution {
public:
    /**
     * @param nums: An integer array sorted in ascending order
     * @param target: An integer
     * @return: An integer
     */
    int lastPosition(vector<int> &nums, int target) {
        // write your code here
        int left=0;
        int right=nums.size()-1;
        if(nums.size()==0)
            return -1;
        while(left+1<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]==target)
                left=mid;
            else if(nums[mid]<target)
                left=mid;
            else if(nums[mid]>target)
                right=mid;
        }
        if(nums[right]==target)
            return right;
        if(nums[left]==target)
            return left;
        return -1;
    }
};
题目三:65 · 两个排序数组的中位数 - LintCode

在这里插入图片描述

思路

对于一个长度为n的排序数组a,若n为奇数,中位数为a[(n/2+1)],若n为偶数,则中位数为(a[n/2]+a[n/2+1])/2,如果我们可以在两个数列中求出第K小的元素,便可以解决该问题。
不妨设数列A元素个数为n,数列B元素个数为m,各自升序排序,求第k小元素。
A[k/2]B[k/2] 比较,如果A[k/2] > B[k/2],那么,所求的元素必然不在B的前k/2个元素中(证明反证法)。反之,必然不在A的前k/2个元素中,于是我们可以将A或B数列的前k/2元素删去,求剩下两个数列的k-k/2小元素,于是得到了数据规模变小的同类问题,递归解决。
如果 k/2 大于某数列个数,所求元素必然不在另一数列的前k/2个元素中,同上操作就好。

class Solution {
public:
    /**
     * @param a: An integer array
     * @param b: An integer array
     * @return: a double whose format is *.5 or *.0
     */
    double found(vector<int> &a, vector<int> &b,int starta,int startb,int k)
    {
        //如果A数组里面没有数
        if(starta>=a.size())
            return b[startb+k-1];
        //如果B数组里面没有数
        if(startb>=b.size())
            return a[starta+k-1];
        //递归的退出条件,如果找最小的数就找两个数组第一个数的较小数
        if(k==1)
            return min(a[starta],b[startb]);
        //如果一个数组太短,那么删除的一定是长的数组的前k/2
        //所以如果数组长度不够,比较值设置为INT_MAX,删除的就是另一个数组的数
        int keya=starta+k/2-1>=a.size()?INT_MAX:a[starta+k/2-1];
        int keyb=startb+k/2-1>=b.size()?INT_MAX:b[startb+k/2-1];
        if(keya>keyb)
            return found(a,b,starta,startb+k/2,k-k/2);
        else
            return found(a,b,starta+k/2,startb,k-k/2);
    }
    double findMedianSortedArrays(vector<int> &a, vector<int> &b) {
        // write your code here
        int sum=a.size()+b.size();
        double ret;
        if(sum&1)
            ret=found(a,b,0,0,sum/2+1);
        else
            ret=(found(a,b,0,0,sum/2)+found(a,b,0,0,sum/2+1))/2;
        return ret;
    }
};
题目四:28 · 搜索二维矩阵 - LintCode

image-20220430183930151

思路:

可以看作是一个有序数组被分成了n段,每段就是一行。因此依然可以二分求解。
对每个数字,根据其下标i,j进行编号,每个数字可被编号为0~n*n-1

相当于是在一个数组中的下标。然后直接像在数组中二分一样来做。取的mid要还原成二位数组中的下标,i = mid/n, j = mid%n

class Solution {
public:
    /**
     * @param matrix: matrix, a list of lists of integers
     * @param target: An integer
     * @return: a boolean, indicate whether matrix contains target
     */
    bool searchMatrix(vector<vector<int>> &matrix, int target) {
        // write your code here
        int n=matrix.size();
        if(maxtrix==NULL||n==0) return false;
        int m=matrix[0].size();
        if(maxtrix[0]==NULL||m==0) return false;
        //进行二分查找
        int left=0;
        int right=n*m-1;
        while(left+1<right)
        {
            int mid=left+(right-left)/2;
            int row=mid/m;
            int col=mid%m;
            if(matrix[row][col]==target)
                return true;
            else if(matrix[row][col]>target)
                right=mid;
            else if(matrix[row][col]<target)
                left=mid;
        }
        if(matrix[left/m][left%m]==target)
            return true;
        if(matrix[right/m][right%m]==target)
            return true;
        return false;
    }
};
题目四:60 · 搜索插入位置 - LintCode

image-20220430183905174

class Solution {
public:
    /**
     * @param a: an integer sorted array
     * @param target: an integer to be inserted
     * @return: An integer
     */
    int searchInsert(vector<int> &a, int target) {
        // write your code here
        if(a.size()==0)
            return 0;
        int left=0;
        int right=a.size()-1;
        while(left+1<right)
        {
            int mid=left+(right-left)/2;
            if(a[mid]==target)  
                return mid;
            else if(a[mid]>target)
                right=mid;
            else if(a[mid]<target)
                left=mid;
        }
        if(target>a[right])
            return right+1;
        if(target>a[left])
            return right;
        return 0;
    }
};
题目五:447 · 在大数组中查找 - LintCode

image-20220430190219784

第一步:

由于一开始无法找到右边界,就把右边界下标设为1。不断成倍的扩大可遍历的数组,直到右边界值大于等于target。

第二步:

套模板

class Solution {
public:
    int searchBigSortedArray(ArrayReader * reader, int target) {
        int left=0,right=1;
        while(reader->get(right)<target)
            right<<=1;
        //在区间内二分查找
        while(left+1<right)
        {
            int mid=left+(right-left)/2;
            if(reader->get(mid)<target)
                left=mid;
            else
                right=mid;
        }
        if(reader->get(left)==target)
            return left;
        if(reader->get(right)==target)
            return right;
        return -1;
    }
};
题目六:159 · 寻找旋转排序数组中的最小值 - LintCode

image-20220430193745103

image-20220430194714865

此题的难点为:需要通过判断出mid的区间,最后在得出和target的关系

class Solution {
public:
    int findMin(vector<int> &nums) {
        int left=0;
        int right=nums.size()-1;
        while(left+1<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>nums[right])
                left=mid;
            else 
                right=mid;
        }
        //相邻时退出
        return min(nums[left],nums[right]);
    }
};
题目七:62 · 搜索旋转排序数组 - LintCode

在这里插入图片描述

这道题是上一道题目的变式。不仅仅要找到mid的区间,还要比较target和mid的关系。

class Solution {
public:
    int search(vector<int> &a, int target) {
        int left=0,right=a.size()-1;
        if(left>right)
            return -1;
        while(left+1<right)
        {
            int mid=left+(right-left)/2;
            //如果mid落在了左区间
            if(a[mid]>a[left])
            {
                //如果target在mid的左边并且不再右区间
                if(target<a[mid]&&a[left]<=target)
                    right=mid;
                else
                    left=mid;
            }
            else
            {
                if(target>a[mid]&&target<=a[right])
                    left=mid;
                else
                    right=mid;
            }
        }
        if(a[left]==target) 
            return left;
        if(a[right]==target)
            return right;
        return -1;
    }
};
题目八:183 · 木材加工 - LintCode

在这里插入图片描述

思路

基于答案值域的二分法。木头的长度范围为(0,max(L)),取一个中间的长度(left+right)/2;计算该长度是否可以分为K根,如果多于K,就变长一点。如果少于K,就变短一点。

class Solution {
public:
    //判断是否长度是否合适
	bool cut(vector<int>&l,int len,int k)
	{
		int count=0;
		for(int i=0;i<l.size();i++)
		{
			count+=l[i]/len;
		}
		if(count>=k)
			return true;
		else
			return false;
	}
    int woodCut(vector<int> &l, int k) {
	    if(l.size()==0)	
	    		return 0;
		int maxl=0;
		for(int i=0;i<l.size();i++)
		{
			maxl=max(maxl,l[i]);
		}
		int start=0,end=maxl;
		while(start+1<end)
		{
			int mid=start+(end-start)/2;
			if(cut(l,mid,k))
			{
				start=mid;
			}
			else
				end=mid;
		}
		if(cut(l,end,k))	return end;
		return start;
    }
};
  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

影中人lx

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

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

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

打赏作者

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

抵扣说明:

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

余额充值