算法-查找

查找

环形数组

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,我们认为两个人是默契的需要满足以下条件:

  1. l小于等于r。

  2. 对于序列中的元素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 圆内随机点

leetcode478

题目

给定圆的半径和圆心的 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();
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值