算法学习系列(一):二分

目录:

  • 引言
  • 一、二分模板
    • 1.非递归模板
    • 2.递归模板
    • 3.二分通用模板
    • 4.测试
  • 二、例题
    • 1.查询最左边的数
    • 2.查询最右边的数
  • 三、详解二分通用模板
  • 四、附录(所有代码)
  • 五、扩展题
    • 1.机器人跳跃问题

引言

不论你是找工作还是考研,不论是什么专业(当然首先要是计算机大类的哈),不论是参加笔试和面试,二分这个问题是必考的,而且非常有可能会让你手撕代码(就是给你一张A4纸让你把代码手写出来),所以这个二分的重要性不言而喻,我这里总结了我目前掌握的所有二分的一个写法,包括两个版本,而且也详细介绍了递归和非递归的模板,其实学习这些模板首先应该理解,然后把它背过,并且之后要反复的重复大量的练习(这才是根本),这样这个东西就跟你背九九乘法表一样,融进你的血液里了,成了你生命中的一部分吧,最后大概就是这么个思想,然后一些比较难的竞赛题,其实也是把各个知识点总结起来了。
为什么要用二分?其实就是快,时间复杂度从O(n)降到O(logn),当然前提是排好序了
啥时候用二分?当满足单调性,只要满足条件的在一个范围里,要找到这个范围的边界点

一、二分模板

1.非递归模板

int BinarySearch01(int nums[], int left, int right, int key)  //非递归写法
{
	while (left <= right)
	{
		int mid = (left + right) / 2;
		//int mid = (right - left) / 2 + left;  这里用减法是因为加法可能会爆int,用减法概率就会小很多
		// int mid = left + right >> 1;  这里也只是写法不同
		if (nums[mid] > key)
		{
			right = mid - 1;
		}
		else if (nums[mid] < key)
		{
			left = mid + 1;
		}
		else
		{
			return mid;
		}
	}

	return -1;  //代表无该值
}

2.递归模板

int BinarySearch02(int nums[], int left, int right, int key)  //递归写法
{
	if (left > right) return -1;

	int mid = left + right >> 1;
	if (nums[mid] > key) return BinarySearch02(nums, left, mid - 1, key);
	else if (nums[mid] < key) return BinarySearch02(nums, mid + 1, right, key);
	
	return mid; 
}

3.二分通用模板

这里的这个通用模板可以解决所有的二分问题

int BinarySearch03(int nums[], int left, int right, int key)  //通用模板
{
	int l = left, r = right;
	while (l < r)
	{
		int mid = l + r >> 1;  //要变条件这里就得变成int mid = l + r + 1 >> 1;
		if (nums[mid] >= key) r = mid;  //当然这里写成<=也是对的,不过条件就得变成l = mid,原因见下文部分三
		else l = mid + 1;  //这里就得变成r = mid - 1
	}

	if (nums[l] != key) return -1;
	return l;  //l和r是一样的
}

4.测试

所得结果全部正确

int main()
{
	int nums[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int n = sizeof(nums) / sizeof(nums[0]);

	//cout << BinarySearch01(nums, 0, n - 1, 1) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, 10) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, 3) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, 7) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, 11) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, -5) << endl;

	//cout << BinarySearch02(nums, 0, n - 1, 1) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, 10) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, 3) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, 7) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, 11) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, -5) << endl;

	cout << BinarySearch03(nums, 0, n - 1, 1) << endl;
	cout << BinarySearch03(nums, 0, n - 1, 10) << endl;
	cout << BinarySearch03(nums, 0, n - 1, 3) << endl;
	cout << BinarySearch03(nums, 0, n - 1, 7) << endl;
	cout << BinarySearch03(nums, 0, n - 1, 11) << endl;
	cout << BinarySearch03(nums, 0, n - 1, -5) << endl;
	return 0;
}

算法学习系列(一):二分_第1张图片

二、例题

1.查询最左边的数

题目:给一段排好序的数,求最左边的数的下标(该数有多个)

int BinarySearch04(int nums[], int left, int right, int key)  //通用模板
{
	int l = left, r = right;
	while (l < r)
	{
		int mid = l + r >> 1;
		if (nums[mid] >= key) r = mid;
		else l = mid + 1;
	}

	if (nums[l] != key) return -1;
	return l;  //l和r是一样的
}

int main()
{
	int nums2[10] = { 0,1,2,3,4,4,4,4,5,6 };
	int n2 = sizeof(nums2) / sizeof(nums2[0]);
	cout << BinarySearch04(nums2, 0, n2 - 1, 4) << endl;  //4
	return 0;
}

在这里插入图片描述

2.查询最右边的数

题目:给一段排好序的数,求最右边的数的下标(该数有多个)

int BinarySearch05(int nums[], int left, int right, int key)  //通用模板
{
	int l = left, r = right;
	while (l < r)
	{
		int mid = l + r + 1 >> 1;
		if (nums[mid] <= key) l = mid;
		else r = mid - 1;
	}

	if (nums[l] != key) return -1;
	return l;  //l和r是一样的
}


int main()
{
	int nums2[10] = { 0,1,2,3,4,4,4,4,5,6 };
	int n2 = sizeof(nums2) / sizeof(nums2[0]);
	cout << BinarySearch05(nums2, 0, n2 - 1, 4) << endl;  //7
	return 0;
}

算法学习系列(一):二分_第2张图片

三、详解二分通用模板

这个二分模板,就是通过砍半和判断条件,把l和r逼到同一个下标,最终这个下标就是想要的结果

int BinarySearch04(int nums[], int left, int right, int key)  //通用模板
{
	int l = left, r = right;
	while (l < r)
	{
		int mid = l + r >> 1;  //这里因为边界问题当下一行是l = mid时就得+1了,详情见第二个例题
		if (nums[mid] >= key) r = mid;  //这里就是if(满足条件) l = mid 或者 r = mid
		else l = mid + 1;  //这里要么是l = mid + 1要么就是r = mid - 1就是看上面的情况
	}

	if (nums[l] != key) return -1;
	return l;  //l和r是一样的
}

四、附录(所有代码)

#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

int BinarySearch01(int nums[], int left, int right, int key)  //非递归写法
{
	while (left <= right)
	{
		int mid = (left + right) / 2;
		//int mid = (right - left) / 2 + left;
		// int mid = left + right >> 1;
		if (nums[mid] > key)
		{
			right = mid - 1;
		}
		else if (nums[mid] < key)
		{
			left = mid + 1;
		}
		else
		{
			return mid;
		}
	}

	return -1;  //代表无该值
}

int BinarySearch02(int nums[], int left, int right, int key)  //递归写法
{
	if (left > right) return -1;

	int mid = left + right >> 1;
	if (nums[mid] > key) return BinarySearch02(nums, left, mid - 1, key);
	else if (nums[mid] < key) return BinarySearch02(nums, mid + 1, right, key);
	
	return mid; 
}

int BinarySearch03(int nums[], int left, int right, int key)  //通用模板
{
	int l = left, r = right;
	while (l < r)
	{
		int mid = l + r >> 1;
		if (nums[mid] >= key) r = mid;
		else l = mid + 1;
	}

	if (nums[l] != key) return -1;
	return l;  //l和r是一样的
}

int BinarySearch04(int nums[], int left, int right, int key)  //通用模板
{
	int l = left, r = right;
	while (l < r)
	{
		int mid = l + r >> 1;
		if (nums[mid] >= key) r = mid;
		else l = mid + 1;
	}

	if (nums[l] != key) return -1;
	return l;  //l和r是一样的
}

int BinarySearch05(int nums[], int left, int right, int key)  //通用模板
{
	int l = left, r = right;
	while (l < r)
	{
		int mid = l + r + 1 >> 1;
		if (nums[mid] <= key) l = mid;
		else r = mid - 1;
	}

	if (nums[l] != key) return -1;
	return l;  //l和r是一样的
}



int main()
{
	int nums[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int n = sizeof(nums) / sizeof(nums[0]);

	//cout << BinarySearch01(nums, 0, n - 1, 1) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, 10) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, 3) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, 7) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, 11) << endl;
	//cout << BinarySearch01(nums, 0, n - 1, -5) << endl;

	//cout << BinarySearch02(nums, 0, n - 1, 1) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, 10) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, 3) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, 7) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, 11) << endl;
	//cout << BinarySearch02(nums, 0, n - 1, -5) << endl;

	//cout << BinarySearch03(nums, 0, n - 1, 1) << endl;
	//cout << BinarySearch03(nums, 0, n - 1, 10) << endl;
	//cout << BinarySearch03(nums, 0, n - 1, 3) << endl;
	//cout << BinarySearch03(nums, 0, n - 1, 7) << endl;
	//cout << BinarySearch03(nums, 0, n - 1, 11) << endl;
	//cout << BinarySearch03(nums, 0, n - 1, -5) << endl;

	int nums2[10] = { 0,1,2,3,4,4,4,4,5,6 };
	int n2 = sizeof(nums2) / sizeof(nums2[0]);
	//cout << BinarySearch04(nums2, 0, n2 - 1, 4) << endl;  //4
	cout << BinarySearch05(nums2, 0, n2 - 1, 4) << endl;  //7
	return 0;
}

五、扩展题

1.机器人跳跃问题

机器人正在玩一个古老的基于 DOS 的游戏。
游戏中有N+1座建筑——从0到N编号,从左到右排列。编号为 0的建筑高度为 0个单位,编号为 i的建筑高度为 H(i)个单位。
起初,机器人在编号为 0的建筑处。每一步,它跳到下一个(右边)建筑。
假设机器人在第 k个建筑,且它现在的能量值是 E,下一步它将跳到第 k+1个建筑。如果 H(k+1)>E,那么机器人就失去 H(k+1)−E的能量值,
否则它将得到 E−H(k+1)的能量值。
游戏目标是到达第 N个建筑,在这个过程中能量值不能为负数个单位。
现在的问题是机器人至少以多少能量值开始游戏,才可以保证成功完成游戏?

输入格式
第一行输入整数 N,第二行是 N个空格分隔的整数,H(1),H(2),,H(N)代表建筑物的高度。
输出格式
输出一个整数,表示所需的最少单位的初始能量值上取整后的结果。

数据范围
1≤N,H(i)1e5

思路:

当能量为e时,下一个多:E = E - (H[k+1] - E) -> E = 2 * E - H[k+1]
              下一个少:E = E + E - H[k+1] -> E = 2 * E - H[k+1]
所以不论下一个多还是少,公式都是一样的。

其次最后的结果满足的话,那么比它大的数肯定也是满足条件的,所以这就跟那个第一个例题一样了,
找最左边的数,只不过要加个check()是否满足条件,然后最小从0开始,最大的值肯定是1e5了,因为
H[i]最大为1e5,那么之后肯定都是加法了,肯定不会做减法,从而也就不会 < 0 了,那么直接看代码 
#include 
#include 

using namespace std;

const int N = 1e5 + 10;  //防止边界问题

int h[N];
int n;

bool check(int e)
{
    for(int i = 1; i <= n; ++i)
    {
        e = 2 * e - h[i];
        if(e >= 1e5) return true;  //若当前已经超过了1e5根据之前的思路可以直接返回了
        if(e < 0) return false;
    }
    
    return true;
}

int main()
{
    scanf("%d", &n);  
    for(int i = 1; i <= n; ++i) scanf("%d", &h[i]);  //数据量大,所以推荐用scanf读比较快一些
    
    int l = 0, r = 1e5;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    
    printf("%d\n", l);
    
    return 0;
}

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