记2015年的腾讯校招经历——技术篇

4 篇文章 0 订阅
2 篇文章 0 订阅

这一篇博客主要是与状态篇形成互补。主要记下一些面试中的题目,并给出题解。
作者注:所有的题目都只给出思考方法,不给出代码。


笔试:

Q:给出一篇英语文章,并统计里面每个单词的出现次数。

A:这题,我用OC给出了一个解法。就是用字典,< key, value >中的key为单词名,value为单词出现的次数,通篇扫描一次文章,那么字典里就存了每个单词出现的次数。
这是个可行的办法,但是这样子呢,就使用了苹果的库里的东西——NSDictionary,那么在笔试完成后,我和朋友讨论这个问题,朋友给出了一个更为亮眼的解法,下面来解释一下思路。
其实就是,朋友构建了一个字典树(Trie树)。
我按照朋友的意思,大概还原了一下节点结构体的模样:

struct Node {
    char c;
    int count; //统计当前单词出现的次数
    Node *sib; //兄弟节点的指针
    Node *next; //进入到下一个层级的节点的指针
};

下面给出一个英文语句:I am going to drink a cup of coffee.
针对这句话,我手绘了生成的Trie树的一部分,希望能表达清楚意思:
这里写图片描述
(实线表示next指针,虚线表示sib指针)
从图中的字典树可以看到,a这个单词出现1次,am出现了1次,cup出现了1次,coffee出现了1次,drink出现了1次等等。
那么,根据这个数据结构,在通篇扫描完文章之后,就可以建立起该Trie树。之后遍历这棵树就可以得到文章中出现单词的次数。而这棵树,实际上是一颗26叉树。


Q:给出10亿个无序的32位整型数,求出它们的中位数。

A:这是笔试里的最后一题,我自己想了一个办法,但在笔试时没细想,只给出了思路。
建立1000个数组(分别标识为-500, -499…1, 2, 3, 4…499)——这样标识是有原因的,至于为什么要这样标识,下文会讲到。
确保-499数组中的数大于-500数组中的数,-498数组中的数大于-499数组中的数等等,以此类推。
接下来,就要将这10亿个无序32位整数划分到这1000个数组中了,划分的方法我也想好了,这很关键。

  1. 首先,设定一个常数等于const int cst = int32_MAX(32位整数的最大值) / 500。
  2. 遍历给定的10亿个数,每个数设为 ai ,让 ai 除以cst,得出一个标准值std(整型)。
  3. std就会是[-500, 500)中的其中一个数,那么这个std等价于映射到1000个数组中的其中一个数组的标识,所以 ai 就可以通过这个std,塞入对应的数组中。
  4. 遍历过后,10亿个数就可以划分到这1000个数组中。

那么,这1000个数组由小到大排列,数组的大小可知,那么就可以知道中位数处于哪个数组中。
接下来将中位数所处的数组独立处理,设计一个阈值 α (假定为50),如果该数组的大小小于该阈值,就排序,取出中位数即可。
大于阈值,重复上述划分法(当然了,当数组的大小过小时,可以减小划分组数)。
这就是我对这个题目的解法,读者们如果发现有不对的地方希望指正,有更好的办法希望可以点拨一下我。


一面:

Q:给出一个连连看的算法

连连看的规则很简单,其实就是给定两个点,寻找两点之间是否有通路——这是我一开始的看法。然后按照我的这个判断,我给出了一个由两点中的一点出发的深度优先遍历(DFS)的办法,并且已经走过的区域不准再走,这样子时间复杂度应该是O(n*m)[假定连连看矩阵有n*m大]。
面试官听了我这个办法之后,给我了一个提示——连连看中两个相同图标中的连线只能拐两次。

听了这句话后,我没想到这是个提示= =,直接就说遍历的时候做个判断就好了。但其实我错了,后面面试官跟我说了一个十分高效的算法,就是依赖于这个规则的。

下面来说说这个巧妙的算法:
先给出个示意图:
这里写图片描述
图中,两个红点就是两个需要连通的点,然后将红点上下左右可以连通到的最远的点都标记起来(如图灰色线就是所有需要标记的点的路径),由于有只能拐两次这个设定,所以假如其中一个红点的灰色线中的某点能作一条直线到对方红点的灰色线上的某一个点,就证明可以连通,遍历完所有灰色的点都找不到合适的路径,则证明不能连通。

这个方法,巧妙地利用了拐两次以内这个规则,效率上要比DFS快得多。


Q:段式内存管理和页式内存管理的区别

A:

  • 段式内存管理:系统可以给一个应用程序分配很多的段来进行内存管理,但是每一段的大小是不同的,然而每一段都具有逻辑意义——比如代码段、共享数据段等。每一段程序都可以独立编制。而段的内容通过逻辑地址来访问——[段号:段内偏移量]。
  • 页式内存管理:系统可以给一个应用程序分配很多的页来进行内存管理,每页的大小是确定的。通过建立虚拟地址和物理内存地址的一 一对应页表来访问每一页的内容。
  • 现在的机器都很少单独使用段式内存管理或者页式内存管理,基本都使用上述两者的合成品——段页式内存管理。

二面:

Q:给定一个nxm的迷宫,中间有很多阻碍,求从(0,0)走到(n,m)的最短路径。

A:这个我给出的解法是深度优先搜索,然而面试官好像没听懂。。。= =


Q:n级台阶,你一次可以选择走一步或者两步,统计能走到第n级台阶的走法数。

A:这题不难,我直接给出代码好了。

int num = 0;
void count(int n, int now) {
    if (now >= n) {
        if (now == n) num++;
        return;
    }
    count(n, now + 1);
    count(n, now + 2);
}

int main() {
    count(n, 0);
    cout<<"总共有"<<num<<"种走法"<<endl;
    return 0;
}

还有一种解法就是,两步在所有走法中只可能出现0 - n/2次,然后出现0次,出现1次,出现2次……每种情况都可以再运用排列组合公式求得走法数,最后加起来就可以得到总和。


Q:求字符串长度

A:这题,我给了个很一般的算法,就是一个while循环统计字符,遇到\n就停。
但是其实最好的办法是:

unsigned long len(char *str) {
    return sizeof(str) / sizeof(char);
}

=.=崩盘


Q:判断一个数是不是2的n次方

A:我自己想了一个时间复杂度为O(log(n))的办法,这个办法就是:
设一个初始数为2,不断对它进行平方操作,那它就以2, 4, 16, 256…的速度指数性增长。设判断数为514,那么这个算法是这么操作的,发现了514处于2^8~2^16之间,那么接下来在2^8 x 2^8这步平方操作中,对另一半2^8进行和上面类似的操作,就是按[2, 2^2, 2^4, 2^8),这样的方式增长,不断的和2^8乘在一起,再找到514处于2^9~2^10之间,然而9跟10只有1的差距,而514!=2^9且514!=2^10,所以514不是2的n次方。

不知道我有没有讲清楚,要是看不懂可以在评论问我。

后来我上网查了一下,发现了一个更为快捷的方法,时间复杂度是常数。附上代码:

bool isPower2(int num){
    if (num <= 0) return false;
    if (num == 1) return true;
    return (num & (num - 1)) == 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值