算法————二分算法

        本章将会讲解3种二分算法的模板为 : 1.朴素二分算法,2.左结点二分算法,3.右结点二分算法。

  1.算法模板

       1.1朴素二分算法

int bin_search(vector<int> &array,int target)
{
	int n = array.size();
	int left = 0, right = n - 1;
	while (left <= right)
	{
		int  mid = left + (right - left) / 2;//防止越界 == (left + right)/2
		if (array[mid] == target) return mid;
		else if (array[mid] > target) right = mid - 1;
		else if (array[mid] < target) left = mid + 1;
	}
	return -1;//没有target 值,就返回 - 1
}

      2.2寻找区间左、右结点的二分算法

        寻找区间左右端点的时候需要将其划分为不同的范围.

 区间的左端点:target的左边严格小于target,右边是大于等于target,根据 x也就中间值落入区间的不同进行划分,在中间值小于target时,也就是落到了左边的区间我们不需要,直接将left = mid + 1,但是当落到右边的区间时,是大于等于的情况,可能是需要的也可能不需要所以让 right = mid;

还需要注意的是 mid 是(right - left)/2,通过取到特殊的情况为两个的时候,如果是落在了右边,会导致无线的循环,其次循环结束的条件为 left < right,当left == right时就是我们想要的答案,否则会陷入死循环之中。

  区间的右端点:ratget的右边严格大于target,左边是小于等于target,同理如上:大于t不是我们想要的区间直接:right = mid - 1, 小于等于 t时,left = mid;需要注意的是,中间mid的使用条件为:(right - left + 1)/2,这里必须是要加上 + 1,让他落到右边防止left == mid,进入死循环

int bin_search1(vector<int>& array, int target)
{
	//寻找左结点的模板
	int n = array.size();
	int left = 0, right = n - 1;
	while (left < right)
	{
		int mid = left + (right - left) / 2;
		if (array[left] < target) left = mid + 1;
		else right = mid;
	}
	return left;
}
int bin_search2(vector<int>& array, int target)
{
	//寻找右结点的模板
	int n = array.size();
	int left = 0, right = n - 1;
	while (left < right)
	{
		int mid = left + (right - left + 1) / 2;//需要进行 + 1,因为在只有两个数字的时候,+ 1会偏向大的那个数,要不然会发生越界的情况
		if (array[left] > target) right = mid - 1;
		else left = mid;
	}
	return left;//或者是 right 都可以
}

 2.例题

         朴素二分算法不在进行练习,直接来到较难的那一部分。

  2.1 34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

        非常的简单,直接使用前面的模板即可,需要注意的是需要判断一下 n == 0,的边界情况。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        int n = nums.size();
        int left = 0, right = nums.size() - 1;
		vector<int> ret;
        if(n == 0)
        {
            ret.push_back(-1);
            ret.push_back(-1);
            return ret;
        }
		//寻找第一个出现的位置
		while (left < right)
		{
			int mid = left + (right - left) / 2;
			if (nums[mid] < target) left = mid + 1;
			else right = mid;
		}
		//最后left 或者是 right 的位置就是我们结果
		if (nums[left] != target) ret.push_back(-1);
		else ret.push_back(left);
		//寻找第二个位置的结点
		left = 0, right = nums.size() - 1;
		while (left < right)
		{
			int mid = left + (right - left + 1) / 2;
			if (nums[mid] > target) right = mid - 1;
			else left = mid;
		}
		if (nums[left] != target) ret.push_back(-1);
		else ret.push_back(left);
		return ret;
    }
};

 2.235. 搜索插入位置 - 力扣(LeetCode)

         根据题意可以将内容划分为两个部分,左边都是小于target的,右边都是大于或者是等于target的,也就相当于去找一个左边界,若果是没有找到的话,会停留在大于那个数的下标位置,所以就是我们想要的结果。

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;
            }
            if(nums[mid] >= target)
            {
                right = mid;
            }
        }
        if(right == nums.size() - 1 && nums[right] < target) return right + 1;
        return right;
    }
};

2.3 852. 山脉数组的峰顶索引 - 力扣(LeetCode)

        寻找峰值,表示的是在一个数组里面最大的那个,这样就有一个向下下降的趋势。可以很好的分成两段。前面的那一段是后面的数严格的大于前面的数,峰值可能在mid中,让 left = mid,如果后面的大于前面的说明 mid 位置,就一定不是峰值位置。其次 mid 位置的值是要指向右边。因为有 left = right 要不然会陷入死循环。

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) 
    {
         int left = 0, right = arr.size() - 1;
    while(left < right)
    {
        int mid = left + (right - left + 1)/2;
        if(arr[mid] > arr[mid - 1]) left = mid;
        else right = mid - 1; 
    }
    return left;
    }
};

 2.4 162. 寻找峰值 - 力扣(LeetCode)

        这道题跟前面的那道题类似。

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

2.5  153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

        不需要管时进行了多少次,他始终是符合这样的一个规律。左侧的AB始终表示者大于右侧的CD同时也是在单调递增的。所以最后的 C 对应的值就是我们的结果。还学要进行标记一下最后一个值,如果是 nums[mid] > nums[right],就说明当前的位置一定不是我想要的区间、需要left = mid + 1。

class Solution
{
public:
    int findMin(vector<int>& nums)
    {
        int left = 0, right = nums.size() - 1;    
        int x = nums[right];

    while(left < right)
     {
        int mid = left + (right - left) / 2;
        if(nums[mid] > x) left = mid + 1;
        else right = mid;
    }
        return nums[left];
   }
};

3.总结

         二分算法其实是一个简单算法,需要记住另外的两个模板,然后答案如果是具有两段的性质就可以进行二分的算法。

 

         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值