刷题心得:整数二分算法的实现及注意事项

整数的二分查找是一个比较简单的算法,而且时间复杂度可以到O(log n),那么整数的二分主要有两种实现方式

//第一种,将区间[begin,end]分为[begin,mid]和[mid+1,end] 
int bsearch_1(int begin, int end)
{
	while (begin < end)
	{
		int mid = (begin + end) / 2;
		if (check(mid))
			end = mid;
		else
			begin = mid+1;
	}
	return begin;
}
//第二种,将区间[begin,end]分为[begin,mid-1]和[mid,end] |注意此时mid的选取 
int bsearch_2(int begin, int end)
{
	while (begin < end)
	{
		int mid = (begin + end + 1) / 2;
		if(check(mid))
			begin = mid;
		else
			end = mid-1; 
	}
	return begin;
}

接下来通过几个例题来解释一下,下面例题来源于LeetCode,AcWing和蓝桥杯练习系统等,题目难度依次上升

例题1:LeetCode.704.二分查找

原题链接:https://leetcode-cn.com/problems/binary-search

【题目描述】

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。(nums序列所有元素不重复)

【数据范围】

1\leq n\leq 10000

-9999\leq nums[i]\leq 9999

【输入示例】

输入: nums = [-1,0,3,5,9,12], target = 9输出: 4
解释: 9 出现在 nums中并且下标为 4
输入: nums = [-1,0,3,5,9,12], target = 2输出: -1
解释: 2 不存在 nums 中因此返回 -1

【题目分析】

nums是一个升序序列那么我们只需要查找序列中到小于或等于目标数值的最大的数,若这个数等于目标数字则查找到了反之则没有,这道题算是二分的模板题了

【参考代码】

class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int l = 0, r = nums.size() - 1;
        while(l < r)
        {
            int mid = l + r + 1 >> 1;
            if(nums[mid] <= target) l = mid;
            else r = mid - 1;
        }
        if(nums[l] == target) return l;
        else return -1;
    }
};

例题2:LeetCode.374.猜数字大小

原题链接:https://leetcode-cn.com/problems/guess-number-higher-or-lower

【题目描述】

猜数字游戏的规则如下:

每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。
你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

-1:我选出的数字比你猜的数字小 pick < num
1:我选出的数字比你猜的数字大 pick > num
0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num
返回我选出的数字。

【数据范围】

1\leq n\leq 2^{31}-1

1\leq pick\leq n

【输入示例】

示例 1:

输入:n = 10, pick = 6
输出:6


示例 2:

输入:n = 1, pick = 1
输出:1


示例 3:

输入:n = 2, pick = 1
输出:1


示例 4:

输入:n = 2, pick = 2
输出:2

此题依旧是经典的二分查找问题,不过值得注意的是,此题中n的范围,如果使用这种方法的话l+r可能会超出int的存储范围

int mid = (l + r) / 2;
int mid = (l + r + 1)/2;

 那么此时这行代码就可以改为

int mid = l + (r - l) / 2;
int mid = l + 1 + (r - l) / 2;

【参考代码】

class Solution {
public:
    int guessNumber(int n) 
    {
        int l = 0, r = n;
        while(l < r)
        {
            int mid = l + (r - l) / 2;
            if(guess(mid) <= 0) r = mid;
            else l = mid + 1;
        }
        return l;
    }
};

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

原题链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array

(同为AcWing.789.数的范围)

【题目描述】

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

【数据范围】

0\leq nums.length\leq 10^{5}

-10^{9}\leq nums[i],target\leq 10^{9}

【输入样例】

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]


示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]


示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

【问题分析】

本题显然是会用到两次二分查找,分别查找目标数的起始和终止下标,分别要用到两种整数二分,值得注意的是本题的数据范围,依旧存在int溢出的问题

【参考代码】

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        if (nums.size() == 0) return vector<int> {-1, -1};
        int l = 0, r = nums.size() - 1;
        while (l < r)
        {
            int mid = l + (r - l) / 2;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        if (nums[l] != target) return vector<int> {-1,-1};
        vector<int> ans{l};
        l = 0, r = nums.size() - 1;
        while (l < r)
        {
            int mid = l + 1 + (r - l) / 2;
            if (nums[mid] <= target) l = mid;
            else r = mid - 1;
        }
        ans.push_back(r);
        return ans;
    }
};

例题3:蓝桥杯练习系统.算法训练.搬走要石

【问题描述】

住在有顶天的天人Tensi对自己的住处很不满。终于有一天她决定把门前碍眼的要石通通丢掉(怒扔要石)。控制要石自然是很容易的事,不过也会消耗灵力。假设搬走一块质量为1的要石会消耗1点灵力,而且由于要石都是连着放置的缘故所以每次除了搬走一颗,也可以搬走连续的任意数量的要石,自然质量是算在一起的。现在Tensi准备最多使用M次灵力,但是她太懒……所以每次只会使用同量的灵力, 也因为她太烂,所以也不愿意多花一点灵力……现在很懒的Tensi需要你帮她计算最少一次需要消耗多少灵力,能够在M次内把所有要石都丢到人间去。

【输入输出】

第一行两个数N,M,用一个空格分隔。
表示一共有N颗要石需要搬走以及Tensi最多发动M次灵力。
接下来包括N个正整数顺序a_{1},a_{2},......a_{n}表示每一颗要石的质量。

输出一个数T
表示Tensi 每次至少消耗T灵力。
如果无解输出-1.

【数据范围】

1 \leq n\leq 1000

1\leq m\leq 400

1\leq a_{1},a_{2},...a_{n}\leq 40000

0\leq T\leq 10^{6}

【输入样例】

输入:

5 3
1 2 1 1 1

输出:

3

【问题分析】

本题也是搜索问题,使用二分从0到1000000进行搜索,此题的难点在于如何对二分的中值进行判断,大致思路就是叠加,找到能够拿的最大的量,而如果某一块石头的质量大于了mid,很显然mid偏小了。

一块一块搬起石头,直到石头总重量大于mid则总次数cnt加1,最后判断cnt和m的关系就可以知道mid是否满足要求。

值得注意的是,这里判断cnt<m而不是cnt<=m,因为这样才能确保后面二分范围正确划分。

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int n, m, a[N];

bool check(int mid)
{
	int cnt = 0, sum = 0;
	for (int i = 0; i < n;)
	{
		if (sum + a[i] <= mid)
		{
			sum += a[i];
			i++;
		}
		else if (a[i] > mid)
			return false;
		else 
		{
			cnt++;
			sum = 0;
		}
	}
	return cnt < m;
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &a[i]);
	}
	int l = 0, r=1e6, mid;
	while (l < r)
	{
		mid = l + r >>1;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	printf("%d", l);
	return 0;
}

本期就到这了,后面还会有刷题心得分享,记录码代码生活

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值