笔试面试题一(腾讯2019)

题目列表

1.硬币

题目描述

牛家村的货币是一种很神奇的连续货币。

他们货币的最大面额是 n,并且一共有面额为 1,面额为 2… 面额为 n,n 种面额的货币。

牛牛每次购买商品都会带上所有面额的货币,支付时会选择给出硬币数量最小的方案。

现在告诉你牛牛将要购买的商品的价格,你能算出牛牛支付的硬币数量吗? (假设牛牛每种面额的货币都拥有无限个。)

输入格式
共一行,包含两个整数 n 和 m,分别表示货币的最大面额以及商品的价格。

输出格式
一个整数表示牛牛支付的硬币数量。

数据范围
1≤n≤105,
1≤m≤109
输入样例1:
6 7
输出样例1:
2
输入样例2:
4 10
输出样例2:
3

分析

题目要求支付的硬币数量最少的方案,乍一看类似于背包问题,但是一看数据范围,显然动态规划求解会超出内存限制。
从1到n之间所有面额的货币都有的条件简化了本问题,使得本题可以通过贪心实现。比如n = 50,买96元东西,只需要先付最大面额的50,再付46即可,消耗两枚硬币。
具体实现固然可以先对方案数ans加上m / n,表示先付这么多个n面额的硬币,然后再看剩下的价格m % n是不是0,不是0就将ans++。更为简单的做法是给商品价格m加上n - 1,再除以n,这样实现的代码就简洁了许多。

代码

#include 
using namespace std;
int main() {
    int n,m;
    cin>>n>>m;
    cout<<(n + m - 1) / n<

2.奇妙的数列

题目描述

妞妞最近迷上了王者荣耀。

小 Q 得到了一个奇妙的数列,这个数列有无限多项,数列中的第 i 个数字为 i×(−1)i,比如数列的前几项为 −1,2,−3,4,−5…
小 Q 兴奋把这个数列拿去给妞妞看,并希望借此邀请妞妞吃饭。

妞妞想了想,对小 Q 说:“对于这个数列,我每次询问你一个区间,你在 1 秒内把这个区间里的数字的和告诉我,如果你答得上来我就跟你一起去吃饭。”

由于妞妞最近沉迷王者荣耀,已经很久都没理过小 Q 了,所以小 Q 不想失去这次珍贵的机会,你能帮帮他吗?

输入格式
第一行,一个整数 q,表示妞妞的询问次数。

接下来 q 行,每行两个整数 l 和 r,表示妞妞询问的区间的左端点和右端点。

输出格式
共 q 行,每行一个整数,表示妞妞询问的区间和。

数据范围
1≤q≤105,
1≤l≤r≤109
输入样例1:
4
2 4
2 2
3 3
1 5
输出样例1:
3
2
-3
-3
输入样例2:
1
1 1000000000
输出样例2:
500000000

分析

做题时容易有思维惯性,这次的题目就是在不断地诱导我们往错误的方向思考。比如上一题容易想到DP,最后却是贪心求解,本题一看区间和,条件反射就是前缀和,友善的一点就是数据范围打消了我们使用前缀和的念头。
本题考察简单的交错调和级数求和问题。在该数列中任取相邻两项,加起来的结果不是1就是-1,如果区间内元素有偶数个,直接分组求和就是结果了,比如-3 + 4 -5 + 6 = 1*2=2,区间左端点l是奇数,结果就是1乘上区间长度的一半,否则就是-1乘上长度的一半。
对于区间长度是奇数的情况,只需要加上最后一个数即可,题目较为简单,需要注意的是判断条件颇多,在判断相邻两数和时需要判断区间左端点的奇偶性,在加上最后一个数时有要判断右端点的奇偶性,区间长度是r - l + 1,如果漏掉后面的1也会引起计算错误。

代码

#include 
using namespace std;
int main(){
    int q,l,r;
    cin>>q;
    while(q--){
        cin>>l>>r;
        int t = -1;
        if(l % 2)   t = 1;
        int ans = t * (r - l + 1) / 2;
        t = 1;
        if(r % 2)  t = -1;
        if((r - l) % 2 == 0) ans += t * r;
        cout<

3.猜拳游戏

题目描述

小 Q 和牛妹参加一个剪刀石头布的游戏,游戏用卡片来玩,每张卡片是剪刀,石头,布中的一种,每种类型的卡片有无限个。

牛妹从中选了 n 张卡片排成一排,正面朝下,小 Q 也会选择 n 张卡片排成一排,然后小 Q 和牛妹的卡片会依次进行比对,第一张对第一张,第二张对第二张…

如果小 Q 赢,小 Q 会得到一分,现在已知牛妹的每一张牌以及小 Q 最终的得分 s,请问小 Q 有多少种选择卡片的方案(多少不同的排列)

输入格式
第一行包含两个整数 n 和 s。

第二行包含 n 个整数,表示牛妹的每张卡片,每个数在 [0,2] 之间,0 代表石头,1 代表布,2 代表剪刀。

输出格式
输出一个整数,表示总方案数对 109+7 取模后的值。

数据范围
1≤n≤2000,
0≤s≤2000
输入样例:
3 2
0 1 2
输出样例:
6

分析

简要概括性题意就是一个人出剪刀石头布的顺序确定了,另一个人每局赢了得1分,输了或者平局不得分,求另一个人出拳的方案数。
n局游戏中得分s,说明赢了s局,也就是在n中选择s个去赢,剩下的n - s个平局或者输。
题目的出拳顺序是干扰项,没有作用,不管前一个人出的是什么,赢的情况永远只有一种方案,不赢的情况永远只有两种方案。所以总的方案数是C(n,s) + 2n-s
唯一的难点是求组合数,C(n,s) = n! / (n-s)! / s! = n * (n-1) (n-s+1) / s!。
乘法操作好计算,但是在模上一个较大的质数后,就不一定能够整除后面的s!了,求组合数的方法有很多,这里使用两种较为简单的方法。
方法一:C(n,m) = C(n-1,m) + C(n-1,m-1)
利用上面的公式很快就可以计算出组合数了,原理就是n个数中选择m个数可以分为第n个数选与不选两种情况,不选第n个数就是C(n-1,m),选了第n个数就需要在前n-1个数中选择剩下的m-1个数,也就是C(n-1,m-1)。

代码

#include 
#include 
using namespace std;
const int mod = 1e9 + 7,N = 2005;
int f[N][N];
int main() {
    int n,s;
    cin>>n>>s;
    long long ans = 1;
    for(int i = 0;i <= n;i++)   f[i][0] = 1;
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= s && j <= i;j++)
            f[i][j] = (f[i-1][j] + f[i-1][j-1]) % mod;
    ans = f[n][s];
    for(int i = 0;i < n - s;i++)    ans = ans * 2 % mod;
    cout<

方法二:快速幂求乘法逆元
将组合数公式里面的除法转为乘上逆元,a *x % mod = a / b % mod,则x就是需要的乘法逆元,并且可以得出x = bmod-2 % mod。
使用快速幂求解逆元,再调用次求解2s,效率要高于上一种方法。

#include 
#include 
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7,N = 2005;
ll ksm(ll a,ll b){
    ll ans = 1 % mod;
    while(b) {
        if(b & 1)   ans = ans * a % mod;
        b >>= 1;
        a = a * a % mod;;
    }
    return ans;
}
int f[N][N];

int main() {
    int n,s;
    cin>>n>>s;
    ll ans = 1;
    for(int i = n;i >= n - s + 1;i--)   ans = ans * i % mod;
    ll k = 1;
    for(int i = 1;i <= s;i++)   k = k * i % mod;
    ans = ans * ksm(k,mod-2) % mod;
    ans = ans * ksm(2,n-s) % mod;
    cout<

4.气球游戏

题目描述

小 Q 在进行射击气球的游戏,如果小 Q 在连续 T 枪中打爆了所有颜色的气球,将得到一只 QQ 公仔作为奖励。(每种颜色的球至少被打爆一只)。

这个游戏中有 m 种不同颜色的气球,编号 1 到 m。

小 Q 一共有 n 发子弹,然后连续开了 n 枪。

小 Q 想知道在这 n 枪中,打爆所有颜色的气球最少用了连续几枪?

输入格式
第一行包含两个整数 n 和 m。

第二行包含 n 个整数,分别表示每一枪打中的气球的颜色,0 表示没打中任何颜色的气球。

输出格式
一个整数表示小 Q 打爆所有颜色气球用的最少枪数。

如果小 Q 无法在这 n 枪打爆所有颜色的气球,则输出 −1。

数据范围
1≤n≤106,
1≤m≤2000
输入样例:
12 5
2 5 3 1 3 2 4 1 0 5 4 3
输出样例:
6
样例解释
有五种颜色的气球,编号 1 到 5。

游客从第二枪开始直到第七枪,这连续六枪打爆了 5 3 1 3 2 4 这几种颜色的气球,包含了从 1 到 5 的所有颜色,所以最少枪数为 6

分析

本题还是很有意思的一道题的,熟悉解的单调性就可以很快进行求解。本质上就是在数组中求最短的区间,使得该区间内包含m个不同的数。
我们人为的判断时就是逐个遍历数组,然后判断加入该元素到区间元素内,如果遍历到和区间左端点相同的元素时,左端点的元素在区间中就没有意义了,左端点可以右移一位,事实上,只要位于区间左端点的元素在区间的其他地方出现了或者左端点元素是0(代表没打着),就失去了意义,可以被踢出区间了。
剩下的就是如何判断区间内元素包含了所有颜色的气球,我们用cnt[i]表示i在区间中出现的次数,在一种颜色首次加入区间时,我们可以在计数数组上加上1,同时将区间颜色的计数变量t++,注意当没命中时候需要加进区间,却不能增加计数变量t的值。当颜色计数变量达到m时,就可以尝试更新最优解了。
注意这里的计数数组cnt[i]在元素被踢出区间时不需要减少,因为此时区间内还有相同的元素。如果一个元素首次加入区间,其cnt值一定是0,当其被踢出区间时,区间内一定还有其他元素,不会影响计数变量t的更新。下面用类似单调队列的方式来求解本问题。

代码

#include 
#include 
using namespace std;
const int N = 1000005,M = 2005;
int cnt[M],q[N];
int main() {
    int n,m,k;
    scanf("%d%d",&n,&m);
    int tt = -1,hh = 0;
    int ans = 0x3f3f3f3f;
    int t = 0;
    for(int i = 0;i < n;i++) {
        scanf("%d",&k);
        q[++tt] = k;//入队
        if(k && !cnt[k]) t += 1;
        cnt[k]++;
        while(hh <= tt && (cnt[q[hh]] > 1 || !q[hh]))   cnt[q[hh++]]--;//队头元素重复出现或者是0则出队
        if(t == m)  ans = min(ans,tt -hh + 1);
    }
    if(ans == 0x3f3f3f3f)    puts("-1");
    else    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:(其它,面试题)