二分法

目录

一,二分法

1,搜索域

2,泛化

3,利用lower_bound构建模板

4,抽象数组

二,模板代码

三,OJ实战

数组搜索定和

力扣 1. 两数之和

力扣 167. 两数之和 II - 输入有序数组

力扣 454. 四数相加 II(二分+计重)

HDU 2141 Can you find it?(找三个数的和,二分)

有序数组搜索定元

力扣 374. 猜数字大小

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

力扣 2529. 正整数和负整数的最大计数

力扣 1146. 快照数组

CSU 1335 高桥和低桥

旋转数组搜索

力扣 153. 寻找旋转排序数组中的最小值

力扣 33. 搜索旋转排序数组

抽象数组搜索

力扣 410. 分割数组的最大值

力扣 1482. 制作 m 束花所需的最少天数

力扣 ​1802. 有界数组中指定下标处的最大值

力扣 2861. 最大合金数

力扣 2557. 从一个范围内选择最多整数 II

HDU 1969 PIE

CSU 1984 LXX的能力值

HDU 2289 Cup

HDU 2199 Can you solve this equation?

无序数组搜索

力扣 540. 有序数组中的单一元素

力扣 162. 寻找峰值

二维数组搜索

力扣 74. 搜索二维矩阵

力扣 240. 搜索二维矩阵 II 

力扣 1901. 寻找峰值 II

特殊数值的数组搜索

力扣 剑指 Offer 53 - II. 0~n-1中缺失的数字

力扣 287. 寻找重复数

四,总结


一,二分法

1,搜索域

整数集的二分法:对于有序数组,查找给定的数。

实数域的二分法:对于区间[a,b]上连续不断且f(a)·f(b)<0的函数y=f(x),通过不断地把函数f(x)的零点所在的区间一分为二,使区间的两个端点逐步逼近零点,进而得到零点近似值的方法叫二分法。

可以看出,两种二分法的原理类似但不完全相同:

类似之处是都是对半分查找区间,并在O(1)的时间内确定其中一个区间有目标。

不同之处是,实数二分法用的是介值定理,需要连续但不需要单调,整数二分法用的是单调,不需要连续。

2,泛化

无论是整数搜索,还是实数搜索,都可以统一表述成搜索某个数组(具体的或抽象的),找到一个具体的成员x,x右边的数都满足某个判别式,左边都不满足。

如果把判别式理解为一个返回值为0或1的函数,那么经过这一层映射之后,所有的二分问题都可以表示成搜索一个数组,前面全都是0,后面全都是1,找到第一个1。

PS:对于在[0,1000]范围内,寻找一个函数的零点,精确到0.000001,实际上也是一个长度为1000000000的数组的搜索。

3,利用lower_bound构建模板

搜索一个数组,前面全都是0,后面全都是1,找到第一个1,这就是lower_bound的功能,所以我们只需要完成前面的转化即可。

bool isOk(int x)
{
	return true; //需要自行修改
}
int find(int low, int high)
{
	vector<int>v;
	for (int i = low; i <= high; i++)v.push_back(isOk(i));
	return lower_bound(v.begin(), v.end(), 1) - v.begin() + low;
}

时间复杂度:t * (high-low),其中t是单次调用isOk的时间

一般,我们期望的算法时间是大概不超过1*1000000

在3种情况下,这个模板有致命缺点:

(1)t不是O(1),也就是说isOk比较耗时,如1000000* 1000000

(2)high-low很大,如1*1000000000

(3)t不是O(1)且high-low很大,如1000000*1000000000

那么,如何解决这个问题呢?

实际上,二分的过程不需要用到数组中的所有数,那么isOk的调用次数可以减少。

如果只按照实际去调用isOk,那么总时间是t*log(high-low),

对于t=1000000,high-low=1000000000完全可以支持。

4,抽象数组

所谓的抽象数组,就是不可能把数组全部拿到再去做二分,

要么是因为访问单个元素的时间大于O(1),要么是因为数组非常非常大。

其实力扣 374. 猜数字大小也属于这里的第二种情况,但是因为这个数组只是一点点抽象,不是特别抽象,所以也可以归类到普通二分法。

二,模板代码

template<typename T>
class Bsearch { //寻找[low,high]中最小的满足isOk条件的数,low<=high,返回值范围是[low,high+getGap()]
public:
	T find(T low, T high)
	{
		if (!isOk(high))return high + getGap(high);
		if (isOk(low))return low;
		T mid;
		while (high - low > getGap(low)) {
			mid = (high + low) / 2;
			if (isOk(mid))high = mid;
			else low = mid;
		}
		return high;
	}
private:
	virtual bool isOk(T x) const //若isOk(x)且!isOk(y)则必有y<x
	{
		return x > 0;
	}
	int getGap(int) {
		return 1;
	}
	int getGap(long long) {
		return 1;
	}
	double getGap(double) {
		return 0.00000001;
	}
};

三,OJ实战

数组搜索定和

力扣 1. 两数之和

rust

力扣 167. 两数之和 II - 输入有序数组

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

题目和  力扣OJ 1. 两数之和  基本没啥差别。

class Solution {
public:
	vector<int> twoSum(vector<int>& nums, int target) {
		vector<vector<int>>ans = FindSum(nums, nums, target);
		vector<int>res = DeleteLineWithSameDatas(ans)[0];
		Fjia(res, 1);
		return res;
	}
};

力扣 454. 四数相加 II(二分+计重)

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。

例如:

输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]

输出:
2

解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

class Solution {
public:
	int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
		vector<int> v1 = EverySum(A, B), v2 = EverySum(C, D);
		vector<pair<int, int>>v3 = Fshr(v1), v4 = Fshr(v2);
		vector<int> v5 = DrawFirst(v3), v6 = DrawFirst(v4);
		vector<int> v7 = DrawSecond(v3), v8 = DrawSecond(v4);
		vector<vector<int>> ans = FindSum(v5, v6, 0);
		int res = 0;
		for (int i = 0; i < ans.size(); i++)res += v7[ans[i][0]] * v8[ans[i][1]];
		return res;
	}
};

HDU 2141 Can you find it?(找三个数的和,二分)

Description

Give you three sequences of numbers A, B, C, then we give you a number X. Now you need to calculate if you can find the three numbers Ai, Bj, Ck, which satisfy the formula Ai+Bj+Ck = X. 

Input

There are many cases. Every data case is described as followed: In the first line there are three integers L, N, M, in the second line there are L integers represent the sequence A, in the third line there are N integers represent the sequences B, in the forth line there are M integers represent the sequence C. In the fifth line there is an integer S represents there are S integers X to be calculated. 1<=L, N, M<=500, 1<=S<=1000. all the integers are 32-integers. 

Output

For each case, firstly you have to print the case number as the form "Case d:", then for the S queries, you calculate if the formula can be satisfied or not. If satisfied, you print "YES", otherwise print "NO". 

Sample Input

3 3 3 1 2 3 1 2 3 1 2 3 3 1 4 10

Sample Output

Case 1: NO YES NO

这个题目也提交了好多次

第一种方案是把2个数组的和用set来存,然后对每一个,在第3个数组里面二分查找。

#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
 
int bs(int key,int a[],int length)
{
	int lo = 0, hi =length;
	int mi;
	while (lo <= hi)
	{
		mi = ((hi - lo) >> 1) + lo;//lo和hi比较大的时候相加可能爆int
		if (a[mi] == key) return mi;
		else if (a[mi]<key) lo = mi + 1;
		else hi = mi - 1;
	}
	return -1;//未找到
}
 
 
int main()
{
	int cas = 1;
	while (1)
	{
		int l, m, n;
		cin >> l >> m >> n;
		int *listl = new int[l];
		int *listm = new int[m];
		int *listn = new int[n];
		for (int i = 0; i < l; i++)cin >> listl[i];
		sort(listl, listl + l);
		for (int i = 0; i < m; i++)cin >> listm[i];
		for (int i = 0; i < n; i++)cin >> listn[i];
		set<int> se;
		for (int i = 0; i < m; i++)for (int j = 0; j < n; j++)
			se.insert(listm[i] + listn[j]);
		set<int>::iterator it;
		int s, sum;
		cin >> s;
		cout << "Case " << cas << ":" << endl;
		for (int i = 0; i < s; i++)
		{			
			cin >> sum;
			int temp = 0;
			for (it = se.begin(); it != se.end(); it++)
			{
				if (bs(sum - *it, listl,l-1) >= 0)
				{
					temp = 1;
					break;
				}
			}
			if (temp)cout << "YES";
			else cout << "NO";
			cout << endl;
		}
		cas++;
	}
	return 0;
}

但是结果超时了。

仔细一想,其实用set用处不大,因为25万个int数,根本不会有太多重复的。

所以还是按照学长PPT里面说的那种,2个数组求和,遍历第三个数组。

当然了,计重数就不用了。

#include<iostream>
#include<algorithm>
using namespace std;
 
int bs(int key,int a[],int length)
{
	int lo = 0, hi =length;
	int mi;
	while (lo <= hi)
	{
		mi = ((hi - lo) >> 1) + lo;//lo和hi比较大的时候相加可能爆int
		if (a[mi] == key) return mi;
		else if (a[mi]<key) lo = mi + 1;
		else hi = mi - 1;
	}
	return -1;//未找到
}
 
int main()
{
	int cas = 1;
	while (1)
	{
		int l, m, n;
		cin >> l >> m >> n;
		int *listl = new int[l];
		int *listm = new int[m];
		int *listn = new int[n];
		int *sum = new int[m*n];
		for (int i = 0; i < l; i++)cin >> listl[i];
		for (int i = 0; i < m; i++)cin >> listm[i];
		for (int i = 0; i < n; i++)
		{
			cin >> listn[i];
			for (int j = 0; j < m; j++)sum[i + j*n] = listn[i] + listm[j];
		}
		sort(sum, sum + m*n);
		int s, su;
		cin >> s;
		cout << "Case " << cas << ":" << endl;
		for (int i = 0; i < s; i++)
		{			
			cin >> su;
			int temp = 0;
			for (int i = 0; i < l;i++)
			{
				if (bs(su - listl[i], sum,m*n-1) >= 0)
				{
					temp = 1;
					break;
				}
			}
			if (temp)cout << "YES";
			else cout << "NO";
			cout << endl;
		}
		cas++;
		delete sum;
	}
	return 0;
}

不过,还是超时了。

这个时候,我开始感觉,可能是因为二分查找的函数需要改进。

于是把二分查找的函数变成了

int bs(int key, int a[], int length)
{
	int lo = 0, hi = length;
	if (key<a[0] || key>a[length])return -1;
	int mi;
	while (lo <= hi)
	{
		mi = ((hi - lo) >> 1) + lo;//lo和hi比较大的时候相加可能爆int
		if (a[mi] == key) return mi;
		else if (a[mi]<key) lo = mi + 1;
		else hi = mi - 1;
	}
	return -1;//未找到
}

这个结果时候又变成了Output Limit Exceeded

实在找不出来哪里输出错了,就百度了一下Output Limit Exceeded

刚好看到一个帖子http://bbs.csdn.net/topics/320153052

里面说要尽量把while(1)这种的改成while(cin>>a)这种的

虽然以前在csuoj里面用while(1)没有出现这种问题,不过还是试了一下,结果真的AC了。

有序数组搜索定元

力扣 374. 猜数字大小

猜数字游戏的规则如下:

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

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

示例 1:

输入:n = 10, pick = 6
输出:6
示例 2:

输入:n = 1, pick = 1
输出:1
示例 3:

输入:n = 2, pick = 1
输出:1
示例 4:

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

提示:

1 <= n <= 231 - 1
1 <= pick <= n

class Solution :public Bsearch<long long>{
public:
    int guessNumber(int n) 
    {
        return find(1,n)-1;
    }
private:
    bool isOk(long long x)const
    {
        return guess(x)==-1;
    }
};

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

统计一个数字在排序数组中出现的次数。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:

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

限制:

0 <= 数组长度 <= 50000

代码:

class Solution {
public:
	vector<int> searchRange(vector<int>& nums, int target) {
		auto it1 = lower_bound(nums.begin(),nums.end(),target);
		auto it2 = upper_bound(nums.begin(), nums.end(), target);
		int res1, res2;
		vector<int>ans;
		if (it1 == it2)res1 = res2 = -1;
		else res1 = it1 - nums.begin(), res2 = it2 - nums.begin()-1;
		ans.insert(ans.end(), res1);
		ans.insert(ans.end(), res2);
		return ans;
	}
    int search(vector<int>& nums, int target) {
        vector<int> ans =  searchRange(nums,target);
        return ans[1]-ans[0]+(ans[0]>-1);
    }
};

力扣 2529. 正整数和负整数的最大计数

给你一个按 非递减顺序 排列的数组 nums ,返回正整数数目和负整数数目中的最大值。

  • 换句话讲,如果 nums 中正整数的数目是 pos ,而负整数的数目是 neg ,返回 pos 和 neg二者中的最大值。

注意:0 既不是正整数也不是负整数。

示例 1:

输入:nums = [-2,-1,-1,1,2,3]
输出:3
解释:共有 3 个正整数和 3 个负整数。计数得到的最大值是 3 。

示例 2:

输入:nums = [-3,-2,-1,0,0,1,2]
输出:3
解释:共有 2 个正整数和 3 个负整数。计数得到的最大值是 3 。

示例 3:

输入:nums = [5,20,66,1314]
输出:4
解释:共有 4 个正整数和 0 个负整数。计数得到的最大值是 4 。

提示:

  • 1 <= nums.length <= 2000
  • -2000 <= nums[i] <= 2000
  • nums 按 非递减顺序 排列。

进阶:你可以设计并实现时间复杂度为 O(log(n)) 的算法解决此问题吗?

class Solution {
public:
    int maximumCount(vector<int>& nums) {
        int a=lower_bound(nums.begin(),nums.end(),0)-nums.begin();
        int b=nums.end()-upper_bound(nums.begin(),nums.end(),0);
        return max(a,b);
    }
};

力扣 1146. 快照数组

实现支持下列接口的「快照数组」- SnapshotArray:

  • SnapshotArray(int length) - 初始化一个与指定长度相等的 类数组 的数据结构。初始时,每个元素都等于 0
  • void set(index, val) - 会将指定索引 index 处的元素设置为 val
  • int snap() - 获取该数组的快照,并返回快照的编号 snap_id(快照号是调用 snap() 的总次数减去 1)。
  • int get(index, snap_id) - 根据指定的 snap_id 选择快照,并返回该快照指定索引 index 的值。

示例:

输入:["SnapshotArray","set","snap","set","get"]
     [[3],[0,5],[],[0,6],[0,0]]
输出:[null,null,0,null,5]
解释:
SnapshotArray snapshotArr = new SnapshotArray(3); // 初始化一个长度为 3 的快照数组
snapshotArr.set(0,5);  // 令 array[0] = 5
snapshotArr.snap();  // 获取快照,返回 snap_id = 0
snapshotArr.set(0,6);
snapshotArr.get(0,0);  // 获取 snap_id = 0 的快照中 array[0] 的值,返回 5

提示:

  • 1 <= length <= 50000
  • 题目最多进行50000 次setsnap,和 get的调用 。
  • 0 <= index < length
  • 0 <= snap_id < 我们调用 snap() 的总次数
  • 0 <= val <= 10^9
class SnapshotArray {
public:
	SnapshotArray(int length) {
		snapId = -1, setId = 0;
		indexs.clear();
		vals.clear();
		indexs.resize(length);
		vals.resize(length);
		for (int i = 0; i < length; i++) {
			indexs[i].push_back(0);
			vals[i].push_back(0);
		}
	}

	void set(int index, int val) {
		indexs[index].push_back(++setId);
		vals[index].push_back(val);
	}

	int snap() {
		snapNum.push_back(setId);
		return ++snapId;
	}

	int get(int index, int snap_id) {
		int num = snapNum[snap_id];
		int id = upper_bound(indexs[index].begin(), indexs[index].end(), num) - indexs[index].begin();
		return vals[index][id-1];
	}
	int snapId, setId;
	vector<vector<int>>indexs;
	vector<vector<int>>vals;
	vector<int>snapNum;
};

CSU 1335 高桥和低桥

题目:

Description

有个脑筋急转弯是这样的:有距离很近的一高一低两座桥,两次洪水之后高桥被淹了两次,低桥却只被淹了一次,为什么?答案是:因为低桥太低了,第一次洪水退去之后水位依然在低桥之上,所以不算“淹了两次”。举例说明:

假定高桥和低桥的高度分别是5和2,初始水位为1

第一次洪水:水位提高到6(两个桥都被淹),退到2(高桥不再被淹,但低桥仍然被淹)

第二次洪水:水位提高到8(高桥又被淹了),退到3。

没错,文字游戏。关键在于“又”的含义。如果某次洪水退去之后一座桥仍然被淹(即水位不小于桥的高度),那么下次洪水来临水位提高时不能算“又”淹一次。

输入n座桥的高度以及第i次洪水的涨水水位ai和退水水位bi,统计有多少座桥至少被淹了k次。初始水位为1,且每次洪水的涨水水位一定大于上次洪水的退水水位。
Input

输入文件最多包含25组测试数据。每组数据第一行为三个整数n, m, k(1<=n,m,k<=10^5)。第二行为n个整数hi(2<=hi<=10^8),即各个桥的高度。以下m行每行包含两个整数ai和bi(1<=bi<ai<=10^8, ai>bi-1)。输入文件不超过5MB。
Output

对于每组数据,输出至少被淹k次的桥的个数。
Sample Input

Sample Output

Case 1: 1
Case 2: 3

都说用树状数组做,实际上普通数组就能轻松解决。

当然了,排序+二分当然是不可少的

代码:

<iostream>
#include<algorithm>
using namespace std;
 
int n, h[100005], d[100005];
 
int find(int k)//超过k的第1个位置
{
	int low = 1, high = n+1;
	while (low < high)
	{
		int mid = (low + high) / 2;
		if (h[mid] > k)high = mid;
		else low = mid + 1;
	}
	return low;
}
 
int main()
{
	int  m, k, ca = 0;
	while (cin >> n >> m >> k)
	{
		for (int i = 1; i <= n; i++)
		{
			cin >> h[i];
			d[i] = 0;
		}
		sort(h + 1, h + n + 1);
		int a = 1, b;
		while (m--)
		{
			cin >> b;	
			d[find(b)]--;
			d[find(a)]++;
			cin >> a;
		}
		int ans = (d[1]>=k);
		for (int i = 2; i <= n; i++)
		{
			d[i] += d[i - 1];
			ans += (d[i] >= k);
		}
		cout << "Case " << ++ca << ": " << ans << endl;
	}
	return 0;
}

旋转数组搜索

力扣 153. 寻找旋转排序数组中的最小值

题目:

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

你可以假设数组中不存在重复元素。

示例 1:

输入: [3,4,5,1,2]
输出: 1

示例 2:

输入: [4,5,6,7,0,1,2]
输出: 0

代码:

class Solution : public Bsearch<int> {
public:
	int findMin(vector<int>& nums) {
		this->nums = nums;
		if (nums.empty())return -1;
		int low = 0, high = nums.size() - 1;
		int ans = find(low, high);
		if (ans > high)ans = low;
		return nums[ans];
	}
	bool isOk(int id)
	{
		return nums[0] > nums[id];
	}
	vector<int> nums;
};

力扣 33. 搜索旋转排序数组

题目:

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

代码:

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

PS:

我在大三找实习的时候,面试一家公司做过这个题目。

当时我第一感觉就是O(n)的算法,面试官问我有没有O(log n)的算法,我简单说了大概思路,

面试官还要我把具体分类情况列出来,我没能成功。

其实不难,真的一点不难,但是面试稍微有点紧张,没有理清楚。

抽象数组搜索

力扣 410. 分割数组的最大值

给定一个非负整数数组 nums 和一个整数 k ,你需要将这个数组分成 k 个非空的连续子数组。

设计一个算法使得这 k 个子数组各自和的最大值最小。

示例 1:

输入:nums = [7,2,5,10,8], k = 2
输出:18
解释:
一共有四种方法将 nums 分割为 2 个子数组。 
其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。

示例 2:

输入:nums = [1,2,3,4,5], k = 2
输出:9

示例 3:

输入:nums = [1,4,4], k = 3
输出:4

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 106
  • 1 <= k <= min(50, nums.length)
class Solution:Bsearch<int> {
public:
    int splitArray(vector<int>& nums, int k) {
        this->nums=nums;
        this->k=k;
        return find(0,1000000000);
    }
    bool isOk(int x) const
    {
        int n=1,s=0;
        for(auto a:nums){
            if(a>x)return false;
            s+=a;
            if(s>x)s=a,n++;
        }
        return n<=k;
    }
    vector<int> nums;
    int k;
};

力扣 1482. 制作 m 束花所需的最少天数

给你一个整数数组 bloomDay,以及两个整数 m 和 k 。

现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花 。

花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。

请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。

示例 1:

输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。
现在需要制作 3 束花,每束只需要 1 朵。
1 天后:[x, _, _, _, _]   // 只能制作 1 束花
2 天后:[x, _, _, _, x]   // 只能制作 2 束花
3 天后:[x, _, x, _, x]   // 可以制作 3 束花,答案为 3
示例 2:

输入:bloomDay = [1,10,3,10,2], m = 3, k = 2
输出:-1
解释:要制作 3 束花,每束需要 2 朵花,也就是一共需要 6 朵花。而花园中只有 5 朵花,无法满足制作要求,返回 -1 。
示例 3:

输入:bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3
输出:12
解释:要制作 2 束花,每束需要 3 朵。
花园在 7 天后和 12 天后的情况如下:
7 天后:[x, x, x, x, _, x, x]
可以用前 3 朵盛开的花制作第一束花。但不能使用后 3 朵盛开的花,因为它们不相邻。
12 天后:[x, x, x, x, x, x, x]
显然,我们可以用不同的方式制作两束花。
示例 4:

输入:bloomDay = [1000000000,1000000000], m = 1, k = 1
输出:1000000000
解释:需要等 1000000000 天才能采到花来制作花束
示例 5:

输入:bloomDay = [1,10,2,9,3,8,4,7,5,6], m = 4, k = 2
输出:9
 

提示:

bloomDay.length == n
1 <= n <= 10^5
1 <= bloomDay[i] <= 10^9
1 <= m <= 10^6
1 <= k <= n

二分法
 


class Solution:public Bsearch<int> {
public:
	int minDays(vector<int>& bloomDay, int m, int k) {
		if (bloomDay.size() / m < k)return -1;
		b = bloomDay;
		this->m = m, this->k = k;
		return find(0, 1000000000);
	}
private:
	bool isOk(int ans) const
	{
		int len = 0, m = this->m;
		for (int i = 0; i < b.size(); i++)
		{
			if (b[i] <= ans)
			{
				len++;
				if (len == k)m--, len = 0;
			}
			else len = 0;
		}
		return m <= 0;
	}
	vector<int>b;
	int m, k;
};

力扣 ​1802. 有界数组中指定下标处的最大值

给你三个正整数 n、index 和 maxSum 。你需要构造一个同时满足下述所有条件的数组 nums(下标 从 0 开始 计数):

nums.length == n
nums[i] 是 正整数 ,其中 0 <= i < n
abs(nums[i] - nums[i+1]) <= 1 ,其中 0 <= i < n-1
nums 中所有元素之和不超过 maxSum
nums[index] 的值被 最大化
返回你所构造的数组中的 nums[index] 。

注意:abs(x) 等于 x 的前提是 x >= 0 ;否则,abs(x) 等于 -x 。

示例 1:

输入:n = 4, index = 2,  maxSum = 6
输出:2
解释:数组 [1,1,2,1] 和 [1,2,2,1] 满足所有条件。不存在其他在指定下标处具有更大值的有效数组。
示例 2:

输入:n = 6, index = 1,  maxSum = 10
输出:3
 

提示:

1 <= n <= maxSum <= 109
0 <= index < n

class Solution : public Bsearch<int> {
public:
	int maxValue(int n, int index, int maxSum)
	{
		this->n=n,this->index=index,this->maxSum = maxSum;
		int x = find(1, maxSum);
		return x - 1;
	}
	long long minSum(long long x, long long n)
	{
		if (x <= n) {
			return (x - 1)*x / 2 + n + 1;
		}
		return x * (n + 1) - (n + 1)*n / 2;
	}
	long long minSum(int x)
	{
		return minSum(x, index) + minSum(x, n - 1 - index) - x;
	}
	bool isOk(int x)
	{
		return minSum(x)> maxSum;
	}
	int n;
	int index;
	int maxSum;
};

力扣 2861. 最大合金数

假设你是一家合金制造公司的老板,你的公司使用多种金属来制造合金。现在共有 n 种不同类型的金属可以使用,并且你可以使用 k 台机器来制造合金。每台机器都需要特定数量的每种金属来创建合金。

对于第 i 台机器而言,创建合金需要 composition[i][j] 份 j 类型金属。最初,你拥有 stock[i] 份 i 类型金属,而每购入一份 i 类型金属需要花费 cost[i] 的金钱。

给你整数 nkbudget,下标从 1 开始的二维数组 composition,两个下标从 1 开始的数组 stock 和 cost,请你在预算不超过 budget 金钱的前提下,最大化 公司制造合金的数量。

所有合金都需要由同一台机器制造。

返回公司可以制造的最大合金数。

示例 1:

输入:n = 3, k = 2, budget = 15, composition = [[1,1,1],[1,1,10]], stock = [0,0,0], cost = [1,2,3]
输出:2
解释:最优的方法是使用第 1 台机器来制造合金。
要想制造 2 份合金,我们需要购买:
- 2 份第 1 类金属。
- 2 份第 2 类金属。
- 2 份第 3 类金属。
总共需要 2 * 1 + 2 * 2 + 2 * 3 = 12 的金钱,小于等于预算 15 。
注意,我们最开始时候没有任何一类金属,所以必须买齐所有需要的金属。
可以证明在示例条件下最多可以制造 2 份合金。

示例 2:

输入:n = 3, k = 2, budget = 15, composition = [[1,1,1],[1,1,10]], stock = [0,0,100], cost = [1,2,3]
输出:5
解释:最优的方法是使用第 2 台机器来制造合金。 
要想制造 5 份合金,我们需要购买: 
- 5 份第 1 类金属。
- 5 份第 2 类金属。 
- 0 份第 3 类金属。 
总共需要 5 * 1 + 5 * 2 + 0 * 3 = 15 的金钱,小于等于预算 15 。 
可以证明在示例条件下最多可以制造 5 份合金。

示例 3:

输入:n = 2, k = 3, budget = 10, composition = [[2,1],[1,2],[1,1]], stock = [1,1], cost = [5,5]
输出:2
解释:最优的方法是使用第 3 台机器来制造合金。
要想制造 2 份合金,我们需要购买:
- 1 份第 1 类金属。
- 1 份第 2 类金属。
总共需要 1 * 5 + 1 * 5 = 10 的金钱,小于等于预算 10 。
可以证明在示例条件下最多可以制造 2 份合金。

提示:

  • 1 <= n, k <= 100
  • 0 <= budget <= 108
  • composition.length == k
  • composition[i].length == n
  • 1 <= composition[i][j] <= 100
  • stock.length == cost.length == n
  • 0 <= stock[i] <= 108
  • 1 <= cost[i] <= 100
class Solution:public Bsearch<long long> {
public:
	int maxNumberOfAlloys(int n, int k, int budget, vector<vector<int>>& composition, vector<int>& stock, vector<int>& cost) {
		int ans = 0;
		for (auto &v : composition)ans = max(ans,maxNumberOfAlloys(budget, v, stock, cost));
		return ans;
	}
	int maxNumberOfAlloys(int budget, vector<int>& composition, vector<int>& stock, vector<int>& cost) {
		this->budget = budget, this->composition = composition, this->stock = stock, this->cost = cost;
		return find(1, INT_MAX) - 1;
	}
	bool isOk(long long x) const //若isOk(x)且!isOk(y)则必有y<x
	{
		long long s = 0;
		for (int i = 0; i < composition.size(); i++) {
			s += max((long long)0, composition[i] * x - stock[i])*cost[i];
		}
		return s > budget;
	}
	int budget;
	vector<int> composition, stock, cost;
};

力扣 2557. 从一个范围内选择最多整数 II

给你一个整数数组 banned 和两个整数 n 和 maxSum 。你需要按照以下规则选择一些整数:

  • 被选择整数的范围是 [1, n] 。
  • 每个整数 至多 选择 一次 。
  • 被选择整数不能在数组 banned 中。
  • 被选择整数的和不超过 maxSum 。

请你返回按照上述规则 最多 可以选择的整数数目。

示例 1:

输入:banned = [1,4,6], n = 6, maxSum = 4
输出:1
解释:你可以选择整数 3 。
3 在范围 [1, 6] 内,且不在 banned 中,所选整数的和为 3 ,也没有超过 maxSum 。

示例 2:

输入:banned = [4,3,5,6], n = 7, maxSum = 18
输出:3
解释:你可以选择整数 1, 2 和 7 。
它们都在范围 [1, 7] 中,且都没出现在 banned 中,所选整数的和为 10 ,没有超过 maxSum 。

提示:

  • 1 <= banned.length <= 105
  • 1 <= banned[i] <= n <= 109
  • 1 <= maxSum <= 1015
int findK(vector<int>& banned, long long maxSum) {
	int n = banned.size();
	vector<long long> prefixSum(n + 1, 0);
	for (int i = 0; i < n; ++i) {
		prefixSum[i + 1] = prefixSum[i] + banned[i];
	}

	int left = 0, right = n;
	while (left < right) {
		int mid = left + (right - left) / 2;
		long long sum_mid = prefixSum[mid];
		long long target =  ((1LL + banned[mid]) * banned[mid]) / 2-maxSum;
		if (sum_mid <= target) {
			right = mid;
		}
		else {
			left = mid + 1;
		}
	}
	return left;
}
class Solution {
public:
	int maxCount(vector<int>& banned, int n, long long maxSum) {
		banned.push_back(0);
		sort(banned.begin(), banned.end());
        banned.erase(unique(banned.begin(), banned.end()), banned.end());
		int k = findK(banned, maxSum) - 1;
		long long bn = banned[k] + 1;
		maxSum -= bn * (bn - 1) / 2;
		for (int i = 0; i <= k; i++)maxSum += banned[i];
		int t = int(sqrt(bn*bn - bn + 0.25 + maxSum * 2) - bn - 0.5);
		return banned[k] - k + min(t + 1, max(0, n - banned[k]));
	}
};

HDU 1969 PIE

题目:

Description

My birthday is coming up and traditionally I'm serving pie. Not just one pie, no, I have a number N of them, of various tastes and of various sizes. F of my friends are coming to my party and each of them gets a piece of pie. This should be one piece of one pie, not several small pieces since that looks messy. This piece can be one whole pie though. 
My friends are very annoying and if one of them gets a bigger piece than the others, they start complaining. Therefore all of them should get equally sized (but not necessarily equally shaped) pieces, even if this leads to some pie getting spoiled (which is better than spoiling the party). Of course, I want a piece of pie for myself too, and that piece should also be of the same size. 
What is the largest possible piece size all of us can get? All the pies are cylindrical in shape and they all have the same height 1, but the radii of the pies can be different. 

Input

One line with a positive integer: the number of test cases. Then for each test case: 
---One line with two integers N and F with 1 <= N, F <= 10 000: the number of pies and the number of friends. 
---One line with N integers ri with 1 <= ri <= 10 000: the radii of the pies. 

Output

For each test case, output one line with the largest possible volume V such that me and my friends can all get a pie piece of size V. The answer should be given as a floating point number with an absolute error of at most 10^(-3).

Sample Input

3 3 3 4 3 3 1 24 5 10 5 1 4 2 3 4 5 6 5 4 2

Sample Output

25.1327 3.1416 50.2655

这个题目其实和求方程的零点差不多,只是判断条件不一样而已。

对每个实数mid,判断能不能每个人都分到大小为mid的pie(忽略了pi,最后再算)

然而我其实是败在了题意上面,题目应该是要求四舍五入保留四位小数,然而Output里面写的明明不是这样,被坑了。

总之,用cout << fixed << setprecision(4) << mid*acos(-1.0) << endl;保留4位小数,它会自动四舍五入,题目就解决了。

代码:


#include<iostream>
#include<math.h>
#include<iomanip>
using namespace std;

class Solution:public Bsearch<double>
{
public:
	Solution(int *list,int n,int f)
	{
		this->list = list;
		this->n = n;
		this->f = f;
	}
private:
	bool isOk(double mid) const
	{
		long long sum = 0;
		for (int i = 0; i < n; i++)sum += (long long)(list[i] / mid);
		return sum <= f;
	}
	int *list;
	int n, f;
};

int main()
{
	int t;
	cin >> t;
	int n, f, ni;
	double low, high, mid;
	while (t--)
	{
		cin >> n >> f;
		int *list = new int[n];
		low = 0;
		high = 0;
		for (int i = 0; i < n; i++)
		{
			cin >> ni;
			list[i] = ni * ni;
			if (list[i] > high)high = list[i];
		}
		cout << fixed << setprecision(4) << Solution(list,n,f).find(0.00001, high) * acos(-1.0) << endl;
	}
	return 0;
}

CSU 1984 LXX的能力值

题目:

Description

LXX学习了N种算法知识,并且对于不同的算法知识掌握的程度不一样。为了能够在比赛中取得更好的成绩,他需要把自己的弱项填补。 就像木桶一样,能盛多少水,并不取决于桶壁上最高的那块木板,而恰恰取决于桶壁上最短的那块。已知LXX对第i种算法知识的能力值为Ai。LXX去向好心的上帝求救,上帝送给了他一个修补工具,但是最多只能使用M(M*L<N)次,且只能使连续的不超过L种知识的能力值提高至任意数值。现在问如何修补才能使能力值最小的最大呢?能力值的序列可以看成跟木桶类似的环状。

Input

第1行包含3个正整数,N, M, L。1≤N≤1000,1≤L≤20
第2行包含N个正整数,A1...Ai...An,1≤Ai≤1000000000

Output

每行输出结果。

Sample Input

8 2 3
8 1 9 2 3 4 7 5

Sample Output

7

代码:

#include<iostream>
using namespace std;
 
int n, m, l;
int a[2001];
 
bool f(int *b,int k)//b[1]-b[n]
{
	int times = m;
	for (int i = 1; i <= n; i++)
	{
		if (b[i] >= k)continue;
		i += l - 1, times--;
	}
	return times>=0;
}
 
bool f(int k)
{
	for (int i = 0; i < n; i++)if (f(a + i,k))return true;
	return false;
}
 
int main()
{
	int maxx, minn, mid;
	while (cin >> n >> m >> l)
	{
		maxx = 1, minn = 1;
		for (int i = 1; i <= n; i++)
		{
			cin >> a[i];
			a[i + n] = a[i];
			if (maxx < a[i])maxx = a[i];
		}
		while (minn < maxx - 1)
		{
			mid = (maxx + minn) / 2;
			if (f(mid))minn = mid;
			else maxx = mid - 1;
		}
		if (f(maxx))minn = maxx;
		cout << minn << endl;
	}
	
	return 0;
}

HDU 2289 Cup

题目:

Description

一个杯子装了很多水,可以把杯子看成圆台,并给出圆台的底面半径,顶部半径,高还有水的体积,求水的高度。

Input

输入包括T组数据 
每组数据包含一行,并且有四个数r, R, H, V代表圆台底部半径,顶部半径,高度和水的体积。 

1. T ≤ 20. 
2. 1 ≤ r, R, H ≤ 100; 0 ≤ V ≤ 1000,000,000. 
3. r ≤ R. 
4. r, R, H, V 输入 
5. 无空数据

Output

答案为一行,保留6位小数

Sample Input

1
100 100 100 3141562 

Sample Output

99.999024 

我是先按照正常的思路来做的,如果是R==r,也就是圆柱,直接用体积除以底面积就是的了。

否则的话,首先把圆台补齐成大圆锥,补出来的小圆锥的高为h=H*r/(R-r)

最后要求的高设为x,那么x应该满足3*v*h*h=Pi*r*r* (  (x+h)^3-h^3  )

那么x可以直接求出来。

但是,因为h是有分母的,当R和r非常接近时,x的误差是无法估量的。

所以,只能把上面的表达式的h代入,然后继续化简,变成整式,即3*v*H*H = Pi * x * (x*x*(R-r)*(R-r)+3*x*H*r*(R-r)+3*H*H*r*r)

然后直接求它的零点就OK了。

代码:

#include<stdio.h>
#include<math.h>

int main()
{
	int t;
	scanf("%d", &t);
	double r, R, H, v;
	while (t--)
	{
		scanf("%lf%lf%lf%lf", &r, &R, &H, &v);
		double low = 0, high = H;
		double mid;
		while (low + 0.0000001 < high)
		{
			mid = (low + high) / 2;
			double temp = mid * (R - r);
			double t = (temp*(temp + 3 * H*r) + H * H*r*r * 3)*mid*acos(-1.0);
			if (t > 3 * v*H*H) high = mid;
			else low = mid;
		}
		printf("%.6lf\n", mid);
	}
	return 0;
}

HDU 2199 Can you solve this equation?

题目大意:给出等式8* X^4+ 7* X^3+ 2* X^2+ 3 * X +6= Y,请找出他在0和100之间的解(包含0和100)

代码:

#include<stdio.h>

int main()
{
	int t;
	double y;
	scanf("%d", &t);
	const double EPS = 0.0000000001;
	while (t--)
	{
		scanf("%lf", &y);
		if (y<6 || y>807020306)printf( "No solution!\n");
		else if (y == 6)printf("%d\n", 0);
		else if (y == 807020306)printf("%d\n", 100);
		else
		{
			double low = 0, high = 100;
			double m;
			while (low + EPS < high)
			{
				m = (low + high) / 2;
				if (((((m * 8 + 7)*m) + 2)*m + 3)*m + 6>y)high = m;
				else low = m;
			}
			printf("%.4lf\n", m);
		}		
	}
	return 0;
}

错误的地方:

这个题目我提交了好多次,除了与题目无关的语法错误之外,还有

1,忘了printf("%.4lf\n", m);这个本身就是自动四舍五入的。

2,关于最后的解的选取还是不会,不知道如果刚好最后类似low<12.3456<high这种情况该怎么弄。

有想过加一个判断,这种情况特殊处理,不过感觉好复杂。

看了一下下午的杨震宇学长在群里发的文件,有一段的标题是“二分浮点数值解单调函数”,原来把EPS设置的小到极致就可以解决这个问题。

我之前想过EPS小一点,但是没想到需要这么小。

无序数组搜索

力扣 540. 有序数组中的单一元素

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

示例 1:

输入: [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:

输入: [3,3,7,7,10,11,11]
输出: 10
注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。

思路:其实这题并不需要有序的信息,只需要保证不会出现连续3个相同的数即可。

所以我把这个问题归为无序数组的二分搜索问题。

class Solution:public Bsearch<int> {
public:
	int singleNonDuplicate(vector<int>& nums) {
		this->nums = nums;
		int id = find(0, nums.size() - 1);
		return nums[id-1];
	}
private:
	virtual bool isOk(int x)const
	{
		return x && nums[x] == nums[x + x % 2 * 2 - 1];
	}
	vector<int>nums;
};

力扣 162. 寻找峰值

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。

提示:

  • 1 <= nums.length <= 1000
  • -231 <= nums[i] <= 231 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]

思路:按照最简单的单峰场景考虑。

class Solution:public Bsearch<int> {
public:
	int findPeakElement(vector<int>& nums) {
		p = nums.data();
		int id = find(0,nums.size()-1);
		return id?id-1:0;
	}
	bool isOk(int x) const
	{
		return x && *(p+x) < *(p+x-1);
	}
	int *p;
};

二维数组搜索

力扣 74. 搜索二维矩阵

题目:

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:

输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
输出: true
示例 2:

输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
输出: false

代码:

class Solution {
public:
	bool searchMatrix(vector<vector<int>>& matrix, int target) {
		if (matrix.size() == 0)return false;
		if (matrix[0].size() == 0)return false;
		vector<int>head;
		for (auto it = matrix.begin(); it != matrix.end(); it++)
		{
			head.insert(head.end(), (*it)[0]);
		}
		auto it1 = lower_bound(head.begin(), head.end(), target);
		auto it2 = upper_bound(head.begin(), head.end(), target);
		if (it1 != it2)return true;
		if (it1 == head.begin())return false;
		head = matrix[it1 - head.begin() - 1];
		it1 = lower_bound(head.begin(), head.end(), target);
		it2 = upper_bound(head.begin(), head.end(), target);
		return it1 != it2;
	}
};

力扣 240. 搜索二维矩阵 II 

剑指 Offer 04. 二维数组中的查找

力扣 1901. 寻找峰值 II

一个 2D 网格中的 峰值 是指那些 严格大于 其相邻格子(上、下、左、右)的元素。

给你一个 从 0 开始编号 的 m x n 矩阵 mat ,其中任意两个相邻格子的值都 不相同 。找出 任意一个 峰值 mat[i][j] 并 返回其位置 [i,j] 。

你可以假设整个矩阵周边环绕着一圈值为 -1 的格子。

要求必须写出时间复杂度为 O(m log(n)) 或 O(n log(m)) 的算法

示例 1:

输入: mat = [[1,4],[3,2]]
输出: [0,1]
解释: 3 和 4 都是峰值,所以[1,0]和[0,1]都是可接受的答案。

示例 2:

输入: mat = [[10,20,15],[21,30,14],[7,16,32]]
输出: [1,1]
解释: 30 和 32 都是峰值,所以[1,1]和[2,2]都是可接受的答案。

提示:

  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n <= 500
  • 1 <= mat[i][j] <= 105
  • 任意两个相邻元素均不相等.
class MySearch :Bsearch<int> {
public:
	MySearch(vector<vector<int>>&mat):mat(mat){}
	int findPeakElement() {
		int id = find(0, mat.size() - 1);
		return id ? id - 1 : 0;
	}
	bool isOk(int x) const
	{
		return x && *max_element(mat[x].begin(), mat[x].end()) < *max_element(mat[x - 1].begin(), mat[x - 1].end());
	}
	vector<vector<int>>& mat;
};
class Solution {
public:
	vector<int> findPeakGrid(vector<vector<int>>& mat) {
		int r = MySearch(mat).findPeakElement();
		int c = max_element(mat[r].begin(), mat[r].end()) - mat[r].begin();
		return vector<int>{r, c};
	}
};

特殊数值的数组搜索

力扣 剑指 Offer 53 - II. 0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

示例 1:

输入: [0,1,3]
输出: 2
示例 2:

输入: [0,1,2,3,4,5,6,7,9]
输出: 8
 

限制:

1 <= 数组长度 <= 10000
 

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

力扣 287. 寻找重复数

题目:

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2
示例 2:

输入: [3,1,3,4,2]
输出: 3
说明:

不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。

代码:

class Solution {
public:
	int findDuplicate(vector<int>& nums,int low,int high) {
		if (low >= high)return low;
		int mid = (low + high) / 2;
		int sum = 0;
		for (int i = 0; i < nums.size(); i++)if (nums[i] >= low&&nums[i] <= mid)sum++;
		if (sum>mid - low + 1)return findDuplicate(nums, low, mid);
		return findDuplicate(nums, mid + 1, high);
	}
	int findDuplicate(vector<int>& nums) {
		return findDuplicate(nums, 1, nums.size() - 1);
	}
};

四,总结

二分法,应该是所有算法里面最简单的,就是在一个有序数组中找一个数,非常简单。

但是最近捕捉到了一点细节,还是想用系统化的模型来总结总结。

二分法的基本用法:

(1)在一个无重有序数组中找一个确定的数,返回找到的下标

(2)在一个可重复的有序数组中找一个确定的数,返回这个数出现的第一个下标

(3)在一个可重复的有序数组中找一个确定的数,返回这个数出现的最后一个下标

(4)结合(2)(3)可得,在一个可重复的有序数组中找一个确定的数,返回这个数出现的所有下标

然而,很多题目的二分法是这么用的:1482. 制作 m 束花所需的最少天数

我总结了一下,基本二分法是在一个显式数组中找到一个确定的数

而很多时候,我们是要求满足某种条件的最大的数,所以我们搜索的对象是隐式的解空间数组,搜索的目标就是我们的答案

理解了这个差异,二分法的题目应该都能想到二分了。

搜索解空间数组的时间复杂度是O(T* logJ),其中T是单点验证时间,J是解空间的大小。

一般如果我们能在10^6次计算内完成单点验证,就不会超时。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值