一.招聘岗位
该笔试题的岗位为:实习研发工程师(通用)
二.笔试题目
注:下面的答案都是本人的自己整理的答案。如有不对或不足之处,请批评指正。
1.简答题
给一个单词a,如果通过交换单词中字母的顺序可以得到另外的单词b,那么b是a的兄弟单词,比如单词army和mary互为兄弟单词。
现在要给出一种解决方案,对于用户输入的单词,根据给定的字典找出输入单词有哪些兄弟单词。请具体说明数据结构和查询流程。要求时间和空间效率尽可能的高。
答:
采用“
质数相乘”
数据结构如下:
struct letter{
char data;
int n
};
根据数学定理:任何一个大于1的自然数N,都可以唯一分解成有限个质数的乘积N=(P_1^a1)*(P_2^a2)......(P_n^an) ,这里P_1<P_2<...<P_n是质数,且唯一。
例如:
a=2 b=3 c=5 d=7 e=11...
f(abcd)=2*3*5*7=210
然后字典里找乘积210的位数相同的一定是这4个字母组合的单词就是兄弟单词。
2.简答题
线程和进程的区别和联系?如何理解“线程安全”问题?
答:
区别和联系:
进程由进程控制块PCB和地址空间两部分组成。进程,是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位。每一个进程都有一个自己的地址空间,即进程空间或(虚空间)。进程空间的大小只与处理机的位数有关,一个16位长处理机的进程空间大小为216,而32位处理机的进程空间大小为232。进程至少有5种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。
线程由线程控制块TCB和线程栈两部分组成。线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻型进程或轻量级进程,也是CPU 调度的一个基本单位。
二者大致的区别:
进程的执行过程是线状的,尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。这是进程宏观上的执行过程。而进程又可有单线程进程与多线程进程两种。我们知道,进程有一个进程控制块PCB ,相关程序段和该程序段对其进行操作的数据结构集这三部分,单线程进程的执行过程在宏观上是线性的,微观上也只有单一的执行过程;而多线程进程在宏观上的执行过程同样为线性的,但微观上却可以有多个执行操作(线程),如不同代码片段以及相关的数据结构集。线程的改变只代表了CPU 执行过程的改变,而没有发生进程所拥有的资源变化。除了 CPU之外,计算机内的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。与进程控制表和 PCB 相似,每个线程也有自己的线程控制表TCB ,而这个 TCB 中所保存的线程状态信息则要比 PCB表少得多,这些信息主要是相关指针用堆栈(系统栈和用户栈),寄存器中的状态数据。进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
线程可以有效地提高系统的执行效率,但并不是在所有计算机系统中都是适用的,如某些很少做进程调度和切换的实时系统。使用线程的好处是有多个任务需要处理机处理时,减少处理机的切换时间;而且,线程的创建和结束所需要的系统开销也比进程的创建和结束要小得多。最适用使用线程的系统是多处理机系统和网络系统或分布式系统。
(1). 线程的执行特性
线程只有 3 个基本状态:就绪,执行,阻塞。
线程存在 5 种基本操作来切换线程的状态:派生,阻塞,激活,调度,结束。
线程安全:
如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。线程安全问题都是由全局变量及静态变量引起的。若每个进程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
3.简单题
C和C++中如何动态分配和释放内存?他们的区别是什么?
答:
C使用 malloc 申请,free 释放。C++使用new申请,delete释放。
因为C++是面向对象的语言,new操作符不但会分配内存空间,还会调用对应类型的构造函数。同样的,delete的时候会调用析构函数。
4.算法与程序设计
网络爬虫在抓取网页时,从指定的url站点入口开始爬取这个站点上的所有urllink,抓取到下一级link对应的页面后,同样对该页面上的link进行抓取从而完成深度遍历。为简化问题,我们假设每个页面上至多只有一个link,如从www.baidu.com/a.html链到www.baidu.com/b.html再链到www.baidu.com/x.html,但爬虫抓取到某个页面时,有可能再链会到www.baidu.com/b.html,也有可能爬取到一个不带任何link的终极页面。当抓取到相同的url或不包含任何link的终极页面时即完成爬取。爬虫在抓取到这些页面后会建立一个单向链表,用来记录抓取到的页面,如:a.html-> b.html -> x.html ->...... -> NULL。
问:对于爬虫分别从www.baidu.com/x1.html和www.baidu.com/x2.html两个入口开始获得两个单项链表,得到这两个单项链表后,如何判断他们是否抓取到了相同的url?(假设页面url上百亿,存储资源有限,无法用hash方法判断其是否包含相同的url)
请先描述相应的算法,再给出相应的代码实现。(只需给出判断方法代码,无需爬虫代码)
答:
方法一:(海量信息的处理方法)
将两个单项链表进行hash映射来划分,如取模为1000,即hash(url)00,则将一个单项链表划分为1000个小文件(a0,a1,a2,a3,....,a999),其中相同的url会在同一个小文件中。如果感觉划分后的文件仍太大,可以将模取得更大一些,比如10000。采取同样的hash函数,以同样的方式处理第二个单项链表,得到小文件(b0,b1,b2,b3,...,b999)。
然后处理每个对应的小文件(ai和bi),即求ai和bi中相同的url。此时可以采用hash统计的方法,求二者的交集;或者采用bloomfilter求交集。
方法二:
假设两个单项链表抓取到了相同的url,由于一个页面只有一个link,所以抓取到相同的url后,该url后面链接的url必然相同。所以,我们只需判断两个单项链表的最后一个节点的url是否相同,如果相同,则两个单项链表抓取到了相同的url。否则,没有抓取到相同的url。
判断方法代码如下:
//p1刚开始指向第一个链表的表头,p2刚开始指向第二个链表的表头。
while(p1->next!=NULL)p1=p1->next;
while(p2->next!=NULL)p2=p2->next;
if(p1->value==p2->value)cout<<"抓取到了相同的url.";
有网友提出抓取到相同的url,链表尾不一定相同:
当抓取到相同的url或不包含任何link的终极页面时即完成爬取。
按照第一种情况,抓取到相同url:
(1)a-b-c-d-c 因为抓取到相同的url c 所以第一个链表为 a-b-c-d-null 最后一个元素是d
(2)e-f-d-c-d 因为抓取到相同的url d 所以第二个链表为 e-f-d-c-null 最后一个元素是c
所以只比较最后一个元素是否相同是不对的
解决办法如下:
用第一个链表的表尾,与第二个链表的各个元素顺序匹配,如果匹配则抓到相同的,否则没有抓到相同的。
说明:如果抓到相同的,则其中一链表表尾,一定在另一链表中出现。
5.算法与程序设计
数组al[0,mid-1] 和al[mid,num-1]是各自有序的,对数组al[0,num-1]的两个子有序段进行merge,得到al[0,num-1]整体有序,要求空间复杂度为o(1)。
注:al[i]元素是支持‘<’运算符的。
答:
两个数组各自有序,合并为一个有序段。如何实现空间复杂度为o(1)。
这里采用插入排序的方法:
void merge(int al[],int mid,int num)
{
for(int i=mid;i<num;i++)
{
temp =a[i];
for(int j=mid-1;a[j]>temp;j--)
{
a[j+1]=a[j];
}
a[j+1]=temp;
}
6.系统设计
相信大家都使用过百度搜索框的suggestion功能(如下图所示)。百度搜索框中的suggestion提示功能如何实现的?请给出实现思路和主要的数据结构、算法。有什么优化思路可以使得时间和空间效率最高?
如下图所示:
答:
统计搜索引擎查询日志,统计各个查询词出现的次数。然后统计查询串中可能出现的各个前缀子串,然后统计以各个前缀子串开头的查询串的频率。对各个子串,取以其作为前缀的前K个最高频查询串。再以各个子串为KEY,建立倒排索引(或trie树)。定期更新倒排索引(或trie树),即可依据查询频率给出一个比较合理的提示内容。当用户输入时,去倒排索引(或trie树)中进行匹配,然后取其出现频率最高的前K个。