【在线笔试题解题报告系列】网易2017校招内推笔试之编程题【持续更新】

网易今年把内推笔试放在牛客网上办,然后出了一批编程题。

题目在:

http://www.nowcoder.com/test/2252286/summary

http://www.nowcoder.com/test/2252291/summary

一共18个,好多(不同岗位抽3个不同的题的样子)……

慢慢写吧,做一题写一题。


以下题解将假定读者有下列知识,对下面所列举的细节不再赘述。

(如果有必要对此进行教学的,请站内信我)

C/C++的基本语法

同余的基本性质


(如果觉得一些细节还要讲的更具体的,也欢迎站内信)


本文地址:http://blog.csdn.net/fcxxzux/article/details/52138964


饥饿的小易

网址:http://www.nowcoder.com/questionTerminal/5ee8df898312465a95553d82ad8898c3

完整的思考思路:


懒人请跳过前2份代码和讲解,谢谢!


考虑正向枚举,直觉上,直接放弃——对无解的情况,你得每一步都考虑展开,每一步展开就2个分支,10万步最多2^100000个分支,怎么可能算完……

(我们回头看看这个做法)

那就考虑逆向枚举。


但是还有一个问题:位置的下标还是指数级增长的(x->x*4+3或x*8+7)

注意到,贝壳总生长在能被1,000,000,007(之后写作1e9+7)整除的位置。

利用这一点,考虑同余的性质,把下标用%1e9+7后的结果表示(因为我根本不在乎,最后具体数值,我只在意,下标是不是1e9+7的倍数)。


然后来倒着做:

从0(下标是1e9+7的情况)开始倒着推算,正着计算是先乘再加,倒着就要先减再除。

——减成了负数怎么办?

——加上1e9+7,变成等价正数(或者说,计算出其加法逆元)

——不能整除怎么办?就说明不可往后推吗?

——我上来就踩了这个坑……

除法并没有同余的性质,但是我们还有乘法逆元。

/4,在%1e9+7的意义下,等价于乘(1e9+8)/4=(2.5e8+2)

/8,在%1e9+7的意义下,等价于乘(1e9+8)/8=(1.25e8+1)

——然后我们愉快的用乘法替代除法吧!


ok,这样倒推最多100000步,记录每个数有没有被访问过,访问过就不要重复展开(像BFS一样),得到一个表,记录了每个数变为0要多少步

——试验运行一下,发现好像这部分的计算飞快啊!

之后管他来什么数,直接查表,表里没有,就是100000步达不到的,否则100000步可达,输出结果。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
using namespace std;
typedef long long ll;

const int mod=1e9+7;
map dis;

int main(){
    dis[0]=0;
    queue q;
    q.push(0);
    while(!q.empty()){
        int x=q.front();q.pop();
        int y1=(x-7+mod)%mod;
        y1=((long long)(125000001LL)*y1)%mod;
        if(!dis[y1]){
            dis[y1]=dis[x]+1;
            if(dis[y1]<100000){
                q.push(y1);
            }
        }
        int y2=(x-3+mod)%mod;
        y2=((long long)(250000002LL)*y2)%mod;
        if(!dis[y2]){
            dis[y2]=dis[x]+1;
            if(dis[y2]<100000){
                q.push(y2);
            }
        }
    }
    int n;
    while(~scanf("%d",&n)){
        printf("%d\n",dis[n]?dis[n]:-1);
    }
    return 0;
}

这样能通过了。

但是回头思考一下,不对啊!

这么做,最坏情况下也得展开2^100000个点啊,就算倒推角度,明确缩小了范围到1e9+7以内,但是1e9+7仍然很多!

那么正着做呢?

于是勇敢的写一发,直接提交,也通过了!

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
using namespace std;
typedef long long ll;

const int mod=1e9+7;
map dis;

int main(){
    int n;
    while(~scanf("%d",&n)){
        dis[n]=0;
        queue q;
        q.push(n);
        while(!q.empty()){
            int x=q.front();q.pop();
            int y1=(x*8LL+7)%mod;
            if(!dis[y1]){
                dis[y1]=dis[x]+1;
                if(dis[y1]<100000){
                    q.push(y1);
                }
            }
            int y2=(x*4LL+3)%mod;
            if(!dis[y2]){
                dis[y2]=dis[x]+1;
                if(dis[y2]<100000){
                    q.push(y2);
                }
            }
        }
        printf("%d\n",dis[0]?dis[0]:-1);
    }
    return 0;
}

但是上面2种做法,都很慢,用时显示都有300ms以上了。


继续思考:

既然正着做,去重,不重复展开就能过,那就说明重复非常非常多(你可以试试看在倒着做的代码里,输出有多少能达到的,只有30万个),但是为什么有那么多?

观察变换形式,并做变形:

4x+3=4(x+1)-1

8x+7=8(x+1)-1

如果多层嵌套呢?

y=4x+3

8y+7=8((4(x+1)-1)+1)-1=8(4(x+1))-1=32(x+1)-1

如果你多枚举一些,就会发现,能变换出的数的形式都是:

a(x+1)-1,其中a是2的>=2的幂次数(4、8、16、32、64、……)

我们能否利用这个特点呢?


当然能!

考虑直接枚举那个a,从2^2一直到……等等,最大是2的多少次?

答:直接考虑最大情况,每次变换都选择8x+7那种,也就是,每次a乘上8,也就是说,最坏是(2^3)^100000=2^300000次

所以,枚举a,从2^2次,一直到2^300000次

然后,对每个a检查一下,乘起来结果%1e9+7是不是0,如果是0,说明100000次之内有解

——问:那最小要执行几次变换?

答:我们直接贪心,尽量让a乘8(乘2次8和乘3次4一样大,当然是乘8越多,变换次数越少)

——问:如果我发现a==2^5或a==2^4的时候满足要求,但是5和4才不能表示成3的倍数,怎么办?

答:别忘了你手上还有4x+3的变换(就是a乘4的变换)

对5这种情况,除以3余2,那刚好,用一次乘4的变换就行了

对4这种情况,除以3余1,我们考虑,消去一个乘8的变换,用2个乘4的变换代替并补足。


计算上,直接/3作为结果,如果有余数,就要结果再加1次

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
using namespace std;
typedef long long ll;

const int mod=1e9+7;

int main(){
    int n;
    while(~scanf("%d",&n)){
        int times=4;
        int ans=100001;
        for(int i=1;i<=300000;++i){
            int x=((long long)(n)*times+times-1)%mod;
            if(x==0){
                ans=(i+1)/3+((i+1)%3?1:0);
                break;
            }
            times=times*2%mod;
        }
        printf("%d\n",ans>100000?-1:ans);
    }
    return 0;
}

摆脱了神慢的map之后,这份代码的速度非常理想,用时不足1ms。


不要二

网址:http://www.nowcoder.com/questionTerminal/1183548cd48446b38da501e58d5944eb

完整的思考过程:

首先把欧几里得距离不能等于2这个条件转化一下。

( (x1-x2) * (x1-x2) + (y1-y2) * (y1-y2) ) 的算术平方根不能等于2

也就是(2点x的差值的平方+2点y的差值的平方)的算数平方根不能等于2

考虑差值的平方可能的取值范围:0、1、4、9、……

怎么凑出4呢?

有且仅有1种方案:一个是0,一个是4

转化一下,用自然语言描述:对任何一个放了蛋糕的点(x,y),不能在其上下左右相差2格的地方((x-2,y),(x+2,y),(x,y-2),(x,y+2))放蛋糕。

这样一转化,你连乘法、开平方都不用了,只用加减就能描述了。


但是这个怎么放仍然是很大的问题!

下面用x表示放蛋糕,o表示不放蛋糕

考虑从小规模解决,比如一行:

对宽度为1,直接放

x

对宽度为2,仍然直接放

xx

对宽度为3,中间一个肯定能放,左右两边2选1,那就先选左边的:

xxo

对宽度为4,第1和第3个位置冲突,第2和第4个位置冲突,仍然贪心放左边:

xxoo

对宽度为5,第1和第3个位置冲突,第2和第4个位置冲突,第3和第5个位置冲突

放第3个位置太吃亏了,打死都不放,放第1和第5个位置

第2和第4个位置随便选一个,仍然选靠左的:

xxoox

一直往下延伸,你会发现,下面这个放法一定不吃亏:

xxooxxooxxooxxooxxooxxooxxoo……

(放2个,不放2个,放2个,不放2个,一直延续)

一行的问题解决了,考虑往多行放:

第二行直接照抄第一行,一点事也没有

但是第三行不能照抄(注意y方向也有位置冲突)


转而考虑列方向,也能得到第一列、第二列xxooxxooxxoo延续下去不吃亏

那第三行、第三列怎么办?

我们优先采纳行号、列号小的方案,然后发现第3~4行、第3~4列的2*2区间,他们能放,而且和之前的不冲突!

那就放下去啊!

然后我们发现,第三行和第一行相反的放置,就没有任何问题了,第四行同理,第三、第四列也是这样。

这样不断延伸,我们得到

xxooxxooxxooxxo

xxooxxooxxooxxo

ooxxooxxooxxoox

ooxxooxxooxxoox

xxooxxooxxooxxo

xxooxxooxxooxxo

ooxxooxxooxxoox

ooxxooxxooxxoox

这样的图案

——是不是觉得很像国际象棋棋盘?


这样,你可以选择暴力把整个图案画出来,然后统计多少格子有放蛋糕,作为答案。

当然也可以选择,直接数学计算:

算出第1、第3行各自能放多少

之后因为是每4行循环,直接算循环多少次,差多少就直接补差的行。

这么做,时间复杂度O(1),用时小于1ms的。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        int firstrow=(m/4*2)+((m%4)<2?m%4:2);
        int secondrow=(m/4*2)+((m%4)>2?m%4-2:0);
        int ans=(n/4)*(firstrow+secondrow)*2;
        switch(n%4){
            case 3:ans+=secondrow;
            case 2:ans+=firstrow;
            case 1:ans+=firstrow;
        }
        printf("%d\n",ans);
    }
    return 0;
}

数列还原

网址:http://www.nowcoder.com/questionTerminal/b698e67a2f5b450a824527e82ed7495d

注意到,最多10个数未知。

如果枚举10个数的所有排列呢?

10!=3628800,才400万不到

考虑到,一般认为电脑1s能做1~2亿次运算

那么我们暴力枚举所以排列是可行的。


但是对每个排列,如何计算其顺序对数呢?

第一反应,直接填进去,n^2暴力计算顺序对数,

这个方案有点太离谱了——好慢,要算~5000次,这是受不了的。


我们能不能预先计算好大部分结果,对每个排列只用很小的计算量来解决呢?

考虑拆分顺序对总数

顺序对总数=已经填进去的数之间的顺序对数+没有填进去的数之间的顺序对数+已经填进去和没有填进去的数之间的顺序对数

第一部分:只用算1次就行了,复杂度n^2的都是能轻松接受的

第二部分:对算出来的每种排列,直接暴力计算吧,计算量最多只有10*9/2

第三部分:直接预先算好。预先计算出每个未填的数在每个位置上,能与预先填写好的数组成的顺序对数。

在实现的时候,对每个未填入的数,扫2遍:

一遍从左到右,用一个变量记录,到当前位置比这个未填的数小的数的数量。如果扫到空位,那就记录结果(即,假设这个数填入这个空位)

还有一遍从右到左,来计算比这个未填的数大的数的数量。

这个预处理的计算量很小,10*100*2


最后,枚举每种排列,暴力计算排列内的顺序对数量,然后查询第三部分预处理结果,对一个排列,计算量只有45+10=55,远小于5000多次。

所以最后用时<1ms。(其实我怀疑数据偏弱了……)


最后,我的实现里,加了2个额外的判断

1、k>最大可能顺序对数,直接输出0

2、k<已填入的数之间的顺序对数,直接输出0

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

int a[105];
bool appear[105];
int missidx[15];
int missnum[15];
int misscnt=0;
int smaller[105][105];
int larger[105][105];

int calc_orderedPairs(int *num,int n){
    int ans=0;
    for(int i=1;in*(n-1)/2){
        puts("0");
        return 0;
    }
    for(int i=0;ik){
        puts("0");
        return 0;
    }
    
    //given and not given
    for(int i=0;i=0;--j){
            if(!a[j]){
                larger[j][missnum[i]]=large;
            }
            else if(a[j]>missnum[i])++large;
        }
    }
    
    int ans=0;
    //not given
    do{
        int inner=calc_orderedPairs(missnum,misscnt);
        for(int i=0;i

合唱团

网址:http://www.nowcoder.com/questionTerminal/661c49118ca241909add3a11c96408c8

……想了半天,居然想不出来如何解释状态转移方程是怎么想出来的。这只能说,是套路。

这个思考过程待填,下面直接说状态的定义。

f [ i ] [ j ] [ 最大 / 最小 ]

分别表示,以第i个人为最后一个(也是必选的)人,加上这个人,已经选了 j 个人,最大可能的乘积和最小可能的乘积。

——为什么不是只记录最大的,还要记录最小的?

——因为最小的,很可能是一个负数,有着极大的绝对值,再乘一个负数,就变成最大的正数,也就是最优解了。

然后考虑,这个状态由哪些状态转移过来?

j 人,明显是从j-1个人的状态,最后加1个人(当前考虑的 i )而来。

第 i 人,根据题目要求,编号差不能大于d。那我们就往前观察最多d个人,从i-d到i-1,选了j-1个人中,选择和自己相乘,最大/最小的。

注意考虑边界条件:只选了一个人,就是 i 自己。


最后,解很大,请使用long long(C++)/ long (Java、C#)来保存中间计算结果。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

int a[55];
ll f[55][15][2];

int main(){
    int n,kk,d;
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
    }
    scanf("%d%d",&kk,&d);
    ll ans=0;
    for(int i=1;i<=n;i++){
        f[i][1][0]=f[i][1][1]=a[i];
        for(int j=2;j<=kk;++j){
            for(int k=i-1;k>=max(i-d,1);--k){
                f[i][j][0]=max(f[i][j][0],max(f[k][j-1][0]*a[i],f[k][j-1][1]*a[i]));
                f[i][j][1]=min(f[i][j][1],min(f[k][j-1][0]*a[i],f[k][j-1][1]*a[i]));
            }
        }
        ans=max(ans,max(f[i][kk][0],f[i][kk][1]));
    }
    printf("%lld\n",ans);
    return 0;
}

数字游戏

网址:http://www.nowcoder.com/questionTerminal/876e3c5fcfa5469f8376370d5de87c06

一开始没仔细看,稍微想了一下——






网易你好无聊,抄Google出过的题……

Google的原题地址:http://code.google.com/codejam/contest/4244486/dashboard#s=p2&a=2

看上去google的还强不少!

当时毫不动脑把小数据过掉了,之后过了40多分钟,想出来大数据的正解……


我们直接说弱化版(网易的版本)应该如何思考吧……

既然选择结果与输入顺序无关,那就闭着眼睛先排序……

首先我们必须要1个1,不然连1都凑不出来

有1以后,看看要凑2怎么办?

1、再来个1,1+1=2

2、来个2,直接得到2

如果更大的数呢?2就凑不出来了,不行。

然后考虑凑3:

如果开始是2个1,那我需要来一个1,1+1+1,或者一个2,1+2,或者一个3,直接成3,但是下一个数是4可不行——凑不出来了

如果开始是一个1,一个2,1+2直接得到3


不停往下考虑,你会发现:

如果我现在前面几个数能凑出1~x,对下一个数a

——如果a>x+1,那么x+1就是最小的凑不出来的数

——否则,这个数a能让凑出来的范围变成1~x+a

        ——这个很显然的,只要a+1、a+2、a+3,一直到a+x,就行了。


所以,解法很直接:

排序,然后从最小的到最大,一个个看能否接续下去,扩大从0开始能连续表达的整数范围,如果能,继续往下,否则我们就找到了最小的,不能被表达的整数。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

int a[1005];

int main(){
    int miss=0;
    int n;
    scanf("%d",&n);
    for(int i=0;imiss+1) break;
        miss+=a[i];
    }
    printf("%d\n",miss+1);
    return 0;
}
——顺带一提,练习里好像为了大家都能过,把数据范围削弱到,最多20个数,考试时是100个……

20个的情况下,暴力枚举即可。但是你写暴力枚举,写出来比正解长、跑的比正解慢……


至于上面提到的,google的原始版本,推荐大家自行思考一下,思路类似。

如有需要,google版本的题解在此:http://code.google.com/codejam/contest/4244486/dashboard#s=a&a=2

(我的题解没看懂的,去看google版本的吧)


地牢逃脱

网址:http://www.nowcoder.com/questionTerminal/0385945b7d834a99bc0010e67f892e38

这个题面写的真差!花了一些时间,去揣摩题意,才猜到大概。


“地牢的出口可能在任意某个可以通行的位置上”
还有,“最坏情况下,他需要多少步才可以离开这个地牢”
——这两句话在一起,导致题意很不明确


第一反应:我去,这家伙要猜终点?猜终点的话,所有可行方格踏完才知道终点在哪里都有可能啊!

然后看样例输出,说是3步就行,放在右下角就行,走过去步数是最大的。


猜一发,是要我们找一个点放出口,使得牛牛走出去的最优方案的步数是最大的?

如果是这个题意的话,等价于:

求距离牛牛起始位置最远的点,走到那里所需要的步数。


这个是很传统的广度优先搜索(下简称BFS)的使用。

具体到这个题上,起点最短距离为0,其他点设置成无穷大,然后起点放进队列。

之后,队列非空的时候,取出队列第一个元素,对这个点,尝试向所有数据里给出的步伐方向走一走,能走的走过去,看看能否更新最短距离,能就更新,并把这个目的点放入队列。

最后遍历距离数组,有走不到的空点就输出-1,否则输出最远的点的距离。

没写过的,或者想确认细节的,直接看代码吧。

(还看不懂的,天猫上买一本《信息学奥赛一本通》,看到第二部分 基础算法的第八章 广度优先搜索,自己去学习一下。)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

int n,m;
int direction[55][2];
int dcnt;
char ground[55][55];
int dis[55][55];

struct Point{
    int x,y;
    Point(){}
    Point(int _x,int _y):x(_x),y(_y){}
    Point go(int idx){
        return Point(x+direction[idx][0],y+direction[idx][1]);
    }
    bool isOK(){
        return x>=0&&y>=0&&x q;
    q.push(start);
    while(!q.empty()){
        Point x=q.front();q.pop();
        for(int i=0;idis[x.x][x.y]+1){
                    dis[y.x][y.y]=dis[x.x][x.y]+1;
                    q.push(y);
                }
            }
        }
    }
    int answer=0;
    for(int i=0;i


====================================群众喜闻乐见的大水题在下面======================================

分苹果

网址:http://www.nowcoder.com/questionTerminal/a174820de48147d489f64103af152709

现在要求均分这些苹果,那就先判断总和能否均分,不能均分当然不行。


之后,还有每次只能一口气移动2个苹果

假设就2人,一个人拿5个苹果,一个人拿11个苹果。

现在要他们每个人手拿8个苹果,在每次移动就要移动2个苹果的条件下,这怎么可能啊?最多做到一个人7个,一个人9个

——注意到,每次加减2个苹果,手上的苹果数量的奇偶性不变

那么,每个人一开始手上苹果数量应该和平均值的奇偶性相同,这样才有可能达到目标状态,否则绝对不可能达到。


最后,如果上面2个条件都满足,要移动几次?

只要看苹果多了的人要给出几次就行了,少了的人会有相应的空位的。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

int a[105];

int main(){
    int n,sum=0,avg=0,ans=0;
    scanf("%d",&n);
    for(int i=0;iavg){
            ans+=(a[i]-avg)/2;
        }
    }
    printf("%d\n",ans);
    return 0;
}


星际穿越

网址:http://www.nowcoder.com/questionTerminal/53e4c208b8cf497086ecd65ef45349bb

……我不想写二分怎么办?

答:直接对h开根号,开根号的结果x一定会导致x*x+x>h

那我们往下一点点减小x,减到x*x+x<=h为止。

——其实减的次数很少

因为x^2+x-((x-1)^2+(x-1))=2x

减小了2x,你还没有一个x的空间?我不信。


(为了避免纠结windows和linux上的输出long long的方式的不一致,直接手写输入输出long long的函数)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

template 
inline void scan(T &ret) {
    char c; ret=0;
    while((c=getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') ret=ret*10+(c-'0'),c=getchar();
}

inline void out(ll x) {
   if(x>9) out(x/10);
   putchar(x%10+'0');
}

int main(){
    ll h;
    scan(h);
    ll x=sqrt(h);
    while(x*x+x>h)--x;
    out(x);
    return 0;
}


藏宝图

网址:http://www.nowcoder.com/questionTerminal/74475ee28edb497c8aa4f8c370f08c30

对第一个字符串遍历过去,第二个字符串要一个指针p,表示匹配了几个字符

如果字符相同,p往后移动

最后第一个字符串处理完的时候,看p有没有移动到末尾。


——如果不理解为什么这么做的,

回顾一下编译原理里的状态机。

第二个字符串里每个字符当做一个状态点,相同就后移,不相同就回到自己。

整个状态机最后追加一个终结节点。

如果停在终结节点,表示文本串被接纳,否则不符合,丢弃。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

char stra[100005];
char strb[100005];

int main(){
    gets(stra);
    gets(strb);
    int p=0;
    for(int i=0;stra[i] && strb[p];++i){
        if(stra[i]==strb[p])++p;
    }
    puts(strb[p]?"No":"Yes");
    return 0;
}

下厨房

网址:http://www.nowcoder.com/questionTerminal/ca5c9ba9ebac4fd5ae9ba46114b0f476

把所有字符串存下来,想个办法去重,求剩下来的数量,就好了嘛~

去重办法非常多,

1、排序后扫一遍,和前面的不同就数量+1

2、使用hashset/hashmap/treeset/treemap等,插入完以后计数

3、其他自带函数,比如,骚气的,C# Linq提供的Distinct()走你!

下面给出方案2和3的做法:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

char str[100005];

int main(){
    set s;
    while(~scanf("%s",str)){
        s.insert(str);
    }
    printf("%d\n",s.size());
    return 0;
}

using System;
using System.Linq;
using System.Collections.Generic;
class Program{
    public static void Main(string[] args){
        List s=new List();
        string str;
        while((str=Console.ReadLine())!=null){
            string[] token=str.Split(' ');
            s.AddRange(token);
        }
        Console.WriteLine(s.Distinct().Count());
    }
}

解救小易

网址:http://www.nowcoder.com/questionTerminal/cd763d8541fc4243b8d3b967bb6d6b6a

走到(xi,yi)的最小步数明显是(xi-1)+(yi-1)

只要知道小易到哪个陷阱的最小步数是最小的,就ok了。


——你看大网易多好,坐标范围居然只有1000*1000

出个20亿*20亿算绝赞好吗?

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;

int x[1005];
int y[1005];

int main(){
    int n,ans=INT_MAX;
    scanf("%d",&n);
    for(int i=0;i


你可能感兴趣的:(校招)