算法-查找
查找
环形数组
1. 2021-4-3 约瑟夫环
题目:
有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。
输入描述:
一行,一个正整数n(1<=n<=1000000)。
输出描述:
输出答案。
示例1
输入
5
输出
4
说明
出局的编号依次为3,1,5,2,最后留下的是4
思路:
相当时一个环1->2->3->4->5->1。需要进行n-1次去除。
每一次去除相当于把当前待去除的i之前的元素指向i之后的元素,i则指向为0或空。
有了这个思路我们需要构建一个next[]环形数组,用来保存每个序号后续指向谁。
随后每次前进2次,将第三次待去除的next[i]=0,那么i之前的元素指向i之后的元素。
第二种做法:f(n)=(f(n-1)+k)%n, n为当前有几个人,k为每次喊到几的人需要出去。
将n设置为1~n,则一个人需要出去时,f(1)=0是第0个人出去。
以n=3,k=3为例子。 s=f(1)=0;
i=2 s=(s+k)%i=(0+3)%2=1
i=3 s=(s+k)%i=(1+3)%3=1
由于编号是从1~n,所以最后1+1=2 第二个人需要出去。
代码:
#include<iostream>
#include<vector>
using namespace std;
int main() {
vector<int> next(1000001, 0);
int n; cin >> n;
for (int i = 1; i <= n; ++i) {
if (i < n)next[i] = i + 1;
else next[i] = 1;
}
int tmp = n; int now = 1;
while (tmp > 1) { //n-1次去除
int time = 3;
while (time > 2) {//实际上就前进了一次
now = next[now];
time--;
}
int pre = now;
int post = next[now];
next[pre] = next[post];
next[post] = 0;
now = next[pre];//另一次前进放到这里来了
tmp--;
}
for (int i = 1; i <= n; ++i)if (next[i] != 0)cout << i;
return 0;
}
二分法
1.2021-3-30 构建二分法
题目:
有三种难度的题目难度分别为Easy,Medium,Hard。现在你总共有 E+EM+M+MH+H 道题,各个字符串的含义如下:
E表示有E道题目难度为Easy。
EM表示有EM道题目难度可以为Easy或Medium。
M表示有M道题目难度为Medium。
MH表示有MH道题目难度可以为Medium或Hard。
H表示有H道题目难度为Hard。
你要用这些题目出尽量多的模拟赛,为了保证题目质量且含有一定的区分度,每场模拟赛需要包含Easy,Medium,Hard 三种难度的题目各一道。求你最多能出多少场模拟赛。
输入描述:
一行五个整数E,EM,M,MH,H。
0 <= E+EM+M+MH+H <= 10^18
输出描述:
一行一个数字表示答案
示例1
输入
2 2 1 2 2
输出
3
说明
三组分别是
E + EM + H
E + MH + H
EM + M + MH
思路:
如此之大的数据量,用不到回溯等办法,必定是O(logn)级别的,往二分法上去靠。
试想当E EM M MH H的和为3的倍数时,最大比赛数量为和/3。因此取值应该为[0,和/3]。
通过观察可以发现,EM和MH分别可以分给E和M、M和H。
我们通过在0~和/3的范围内二分查找,当前mid为预想的最大比赛量。
如果E或者H小于当前mid比赛量,我们可以通过EM和MH分给他们。
最后我们同时比较E、H、M是否大于等于mid(注意M和剩下的EM、MH共同组成M)。
如果大于等于mid,意味着我们还可以办更多的比赛。
如果小于mid,意味着预想的比赛量应该减少。
代码:
#include<iostream>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
long long E,EM,M,MH,H;
bool find(long long i){
if(E<i){
long long cur=min(i-E,EM);
E+=cur;
EM-=cur;
}
if(H<i){
long long cur=min(i-H,MH);
H+=cur;
MH-=cur;
}
if(M+EM+MH>=i&&E>=i&&H>=i)return true;
else return false;
}
int main(){
cin>>E>>EM>>M>>MH>>H;
long long l=0;
long long r=(E+EM+M+MH+H)/3;
long long res=0;
while(l<=r){
long long mid=(l+r)/2;
if(find(mid)){
l=mid+1;
res=max(res,mid);
}
else r=mid-1;
}
cout<<res;
return 0;
}
2.2021-4-5 看起来是二叉,但是是二分查找
题目:
The Stern-Brocot tree is an infinite complete binary tree in which the vertices correspond one-for-one to the positive rational numbers, whose values are ordered from the left to the right as in a search tree.
Figure 1 shows a part of the Stern-Brocot tree, which has the first 4 rows. Each node in the tree is marked in a red cycle. The value in the node is the mediant of the left and right fractions. The mediant of two fractions A/B and C/D is defined as (A+C)/(B+D).
To construct the Stern-Brocot tree, we first define the left fraction of the root node is 0/1, and the right fraction of the root node is 1/0. So the value in the root node is the mediant of 0/1 and 1/0, which is (0+1)/(1+0)=1/1. Then the value of root node becomes the right fraction of the left child, and the left fraction of the right child. For example, the 1st node in row2 has 0/1 as its left fraction and 1/1(which is the value of its parent node) as its right fraction. So the value of the 1st node in row2 is (0+1)/(1+1)=1/2. For the same reason, the value of the 2nd node in row2 is (1+1)/(1+0)=2/1. This construction progress goes on infinitly. As a result, every positive rational number can be found on the Stern-Brocot tree, and can be found only once.
Given a rational number in form of P/Q, find the position of P/Q in the Stern-Brocot Tree.
输入描述:
Input consists of two integers, P and Q (1<=P,Q<=1000), which represent the rational number P/Q. We promise P and Q are relatively prime.
输出描述:
Output consists of two integers, R and C.
R indicates the row index of P/Q in the Stern-Brocot Tree, C indicates the index of P/Q in the row.
Both R and C are base 1.
We promise the position of P/Q is always in the first 12 rows of the Stern-Brocot tree, which means R<=12.
示例1
输入
5 3
输出
4 6
思路:
观察一下图 每个节点左右两边分别为 a/b c/d。
a=0,b=1,c=1,d=0。根节点为a+c/b+d。
那么我们要找到某个具体的节点p/q,其实可以用p/q和当前根节点进行比较,转化成二分查找问把a题。根据情况更新 a b c d即可。
代码:
int main() {
double p, q;
cin >> p >> q;
int a = 0; int b = 1; int c = 1; int d = 0;
if (p == 1 && q == 1) {
cout << 1<<" "<<1;
return 0;
}
int h = 1;
double mid = (a + c) *1.0/ (b + d);
vector<bool> v;
while (p * 1.0 / q != mid) {
if (p * 1.0 / q > mid) {
a = a + c;
c = c;
b = b + d;
d = d;
mid= (a + c) * 1.0 / (b + d);
v.push_back(true);
}
else if(p * 1.0 / q<mid){
a = a;
b = b;
c = a + c;
d = b + d;
mid = (a + c) * 1.0 / (b + d);
v.push_back(false);
}
h++;
}
int hh = h-1;
int ans = 0;
for (int i = 0; i < v.size(); ++i) {
if (i == v.size() - 1) {
if(v[i])ans+=2;
else ans+=1;
continue;
}
if (v[i]&&hh-1>=0)ans += pow(2, hh-1);
hh--;
}
cout << h << " " << ans;
return 0;
}
3.2021-4-17 二分法构建对数
题目:
小团从某不知名论坛上突然得到了一个测试默契度的游戏,想和小美玩一次来检验两人的默契程度。游戏规则十分简单,首先有给出一个长度为n的序列,最大值不超过m。
小团和小美各自选择一个[1,m]之间的整数,设小美选择的是l,小团选择的是r,我们认为两个人是默契的需要满足以下条件:
-
l小于等于r。
-
对于序列中的元素x,如果0<x<l,或r<x<m+1,则x按其顺序保留下来,要求保留下来的子序列单调不下降。
小团为了表现出与小美最大的默契,因此事先做了功课,他想知道能够使得两人默契的二元组<l,r>一共有多少种。
我们称一个序列A为单调不下降的,当且仅当对于任意的i>j,满足A_i>=A_j。
输入描述:
输入第一行包含两个正整数m和n,表示序列元素的最大值和序列的长度。(1<=n,m<=100000)
输入第二行包含n个正整数,表示该序列。
输出描述:
输出仅包含一个整数,表示能使得两人默契的二元组数量。
示例1
输入
5 5
4 1 4 1 2
输出
10
思路:
非常经典的二分法题目。
题目要求 留下<l >r的数,nums依旧单调不递减。则lr的对数。
可以先固定r,再去固定l,而此时l必定是从1逐步过来的,通过二分法固定l,使得满足题目要求。
随后l的个数,即是当前r能匹配到的对数。
另外需要注意的是,r在从后往前的过程中,可以先判断>r的数去掉以后是否满足nums。
如果不满足,那么之前的r也不需要判断的,因为必定会满足不了单调不递减。
代码:
#include<bits/stdc++.h>
using namespace std;
bool isinc(vector<int>& tmp) {
for (int i = 0; i + 1 < tmp.size(); ++i) {
if (tmp[i] > tmp[i + 1])return false;
}
return true;
}
int main() {
int m, n;
cin >> m>>n;
vector<int> nums(n, 0);
for (int i = 0; i < n; ++i) {
cin >> nums[i];
}
int r = m; int ans = 0;
while (r >= 1) {
vector<int> tmp;
for (auto& e : nums)if (e > r)tmp.emplace_back(e);
if (!isinc(tmp))break;
int left = 1; int right = r; int cnt = 0;
while (left <= right) {
int mid = (left + right) / 2;
vector<int> tmp;
for (auto& e : nums)if (e > r || e < mid)tmp.emplace_back(e);
if (!isinc(tmp)) {
right = mid - 1;
}
else {
left = mid + 1; cnt = mid;
}
}
ans += cnt;
r--;
}
cout << ans;
return 0;
}
}
中位数
1. 2021-4-18 通过双迭代器寻找中位数
题目:
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
思路:
流中的数据不断地输入,需要找到按大小排序的中位数。
因为无需映射,所以用set。又因为可能有重复元素,使用multiset。
显然每次维护中位数需要Logn,我们想要让查找到o1,得考虑每次插入的时候对迭代器维护。
又因为奇偶数的原因,需要用到两个迭代器。当数组为奇数,low和high指向同一个数,不然指向中间两个数。
我们这样维护:
当原数组为奇数,此时插入有三种情况:
1. *low=num,如1 3 5,插入3,则high++。
2. *low<num,如1 3 5,插入2,则high++。
3. *low>num,如1 3 5,插入4,则low--。
当原数组为偶数,此时插入有三种情况:
1. *low<num且num<*high,如1 2 4 5,插入3,则low++ high--。
2. *high<=num,如1 3 3 5,插入4或者1 3 3 5,插入3,则low++。
2. *low<=num,如1 3 3 5,插入2或者1 3 3 5,插入3,则high--,但low位置不确定,直接让它指向high即可。
代码:
class MedianFinder {
public:
multiset<int> mset;
multiset<int>::iterator low;
multiset<int>::iterator high;
/** initialize your data structure here. */
MedianFinder() {
}
void addNum(int num) {
if(mset.size()==0){
mset.insert(num);
low=mset.begin();
high=mset.begin();
}
else{
int n=mset.size();
mset.insert(num);
if(n%2==1){
if(num<*low){
low--;
}
else high++;
}
else{
if(num>*low&&num<*high){
low++;high--;
}
else if(num<=*low){
low=--high;
}
else low++;
}
}
}
double findMedian() {
return (*low+*high)*0.5;
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
圆
1.2021-4-22 圆内随机点
题目
给定圆的半径和圆心的 x、y 坐标,写一个在圆中产生均匀随机点的函数 randPoint 。
说明:
输入值和输出值都将是浮点数。
圆的半径和圆心的 x、y 坐标将作为参数传递给类的构造函数。
圆周上的点也认为是在圆中。
randPoint 返回一个包含随机点的x坐标和y坐标的大小为2的数组。
思路
没有极坐标,用的是点离圆心的距离公式,求解dx dy。
代码
class Solution
{
public:
double x,y,r;
Solution(double radius, double x_center, double y_center)
{
x=x_center;y=y_center;r=radius;
}
vector<double> randPoint()
{
double dx=r*(rand()%20001-10000)*0.0001;
double dy=r*(rand()%20001-10000)*0.0001;
while(dx*dx+dy*dy>r*r)
{
dx=r*(rand()%20001-10000)*0.0001;
dy=r*(rand()%20001-10000)*0.0001;
}
return {x+dx,y+dy};
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(radius, x_center, y_center);
* vector<double> param_1 = obj->randPoint();
*/