更多服务
百度面试题精选
作者:zhengshifan 日期:2016-06-15 浏览

1、给一个单词a,如果通过交换单词中字母的顺序可以得到另外的单词b,那么ba的兄弟单词,比如的单词armymary互为兄弟单词。
现在要给出一种解决方案,对于用户输入的单词,根据给定的字典找出输入单词有哪些兄弟单词。请具体说明数据结构和查询流程,要求时间和空间效率尽可能地高。
字典树的典型应用,一般情况下,字典树的结构都是采用26叉树进行组织的,每个节点对应一个字母,查找的时候,就是一个字母一个字母的进行匹配,算法的时间复杂度就是单词的长度n,效率很高。因此这个题目可以定义一个字典树作为数据结构来查询的,时间效率会很高,这样就转化为在一棵字典树中查找兄弟单词,只要在字典树中的前缀中在存储一个vector结构的容器,这样查找起来就是常数级的时间复杂度了,效率很高的。。
数据结构可以定义如下:

[cpp] view plaincopy

1. struct word

2. {

3. vector brother; // 用于保存每个单词的兄弟单词

4. word *next[26]; // 字典树中每个节点代表一个字符,并指向下一个字符

5. };

如上述数据结构所示,字典树的建立是在预处理阶段完成的,首先根据字典中的单词来建立字典树,建立的时候,需要稍微特殊处理一下,就是比如potsstoptops互为兄弟单词,那么在字典中按照首字母顺序的话,应该先遇到pots单词,那么我首先对其进行排序,结果是opts,那么字典树中就分别建立4个节点,分别为o->p->t->s,当然这个是不同层次的,在节点s处的vector容器brother中添加单词pots,遇到stop的时候,同样的方法,排序是opts,此时发现这4个节点已经建立了,那么只需要在第四个节点s处的vector容器brother中添加单词stoptops单词的处理方法是同样的。
这样建立完字典树后,查询兄弟单词的效率就会很高了,比哈希的效率还要高;查到tops的兄弟的单词的时候,首先排序,那么就是opts,然后在字典树中查找opts,在s处将其vector容器brother中的的单词输出就是tops的所有兄弟单词。
2、系统中维护了若干数据项,我们对数据项的分类可以分为三级,首先我们按照一级分类方法将数据项分为ABC......若干类别,每个一级分类方法产生的类别又可以按照二级分类方法分为abc......若干子类别,同样,二级分类方法产生的类别又可以按照是三级分类方法分为iiiiii......若干子类别,每个三级分类方法产生的子类别中的数据项从1开始编号。我们需要对每个数据项输出日志,日志的形式是key_value对,写入日志的时候,用户提供三级类别名称、数据项编号和日志的key,共五个key值,例如,write_logA,a,i,1,key1),获取日志的时候,用户提供三级类别名称、数据项编号,共四个key值,返回对应的所有的key_value对,例如get_logA,a,i,1,key1),
请描述一种数据结构来存储这些日志,并计算出写入日志和读出日志的时间复杂度。

3
CC++中如何动态分配和释放内存?他们的区别是什么?
malloc/free和new/delete的区别

4、数组al[0,mid-1]和al[mid,num-1]是各自有序的,对数组al[0,num-1]的两个子有序段进行merge,得到al[0,num-1]整体有序。要求空间复杂度为O(1)。注:al[i]元素是支持'<'运算符的。

[cpp] view plaincopy

1. /*

2. 数组a[begin, mid] a[mid+1, end]是各自有序的,对两个子段进行Merge得到a[begin , end]的有序数组。 要求空间复杂度为O(1)

3. 方案:

4. 1、两个有序段位ABA在前,B紧接在A后面,找到A的第一个大于B[0]的数A[i] A[0...i-1]相当于merge后的有效段,在B中找到第一个大于A[i]的数B[j]

5. 对数组A[i...j-1]循环右移j-k位,使A[i...j-1]数组的前面部分有序

6. 2、如此循环merge

7. 3、循环右移通过先子段反转再整体反转的方式进行,复杂度是O(L), L是需要循环移动的子段的长度

8. */

9. #include

10. using namespace std;

11.

12. void Reverse(int *a , int begin , int end ) //反转

13. {

14. for(; begin < end; begin++ , end--)

15. swap(a[begin] , a[end]);

16. }

17. void RotateRight(int *a , int begin , int end , int k) //循环右移

18. {

19. int len = end - begin + 1; //数组的长度

20. k %= len;

21. Reverse(a , begin , end - k);

22. Reverse(a , end - k + 1 , end);

23. Reverse(a , begin , end);

24. }

25.

26. // 将有序数组a[begin...mid] a[mid+1...end] 进行归并排序

27. void Merge(int *a , int begin , int end )

28. {

29. int i , j , k;

30. i = begin;

31. j = 1 + ((begin + end)>>1); //位运算的优先级比较低,外面需要加一个括号,刚开始忘记添加括号,导致错了很多次

32. while(i <= end && j <= end && i

33. {

34. while(i <= end && a[i] < a[j])

35. i++;

36. k = j; //暂时保存指针j的位置

37. while(j <= end && a[j] < a[i])

38. j++;

39. if(j > k)

40. RotateRight(a , i , j-1 , j-k); //数组a[i...j-1]循环右移j-k

41. i += (j-k+1); //第一个指针往后移动,因为循环右移后,数组a[i....i+j-k]是有序的

42. }

43. }

44.

45. void MergeSort(int *a , int begin , int end )

46. {

47. if(begin == end)

48. return ;

49. int mid = (begin + end)>>1;

50. MergeSort(a , begin , mid); //递归地将a[begin...mid] 归并为有序的数组

51. MergeSort(a , mid+1 , end); //递归地将a[mid+1...end] 归并为有序的数组

52. Merge(a , begin , end); //将有序数组a[begin...mid] a[mid+1...end] 进行归并排序

53. }

54.

55. int main(void)

56. {

57. int n , i , a[20];

58. while(cin>>n)

59. {

60. for(i = 0 ; i < n ; ++i)

61. cin>>a[i];

62. MergeSort(a , 0 , n - 1);

63. for(i = 0 ; i < n ; ++i)

64. cout<" ";

65. cout<

66. }

67. return 0;

68. }

5、线程和进程的区别及联系?如何理解线程安全问题?

答案:进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。
1、简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2、线程的划分尺度小于进程,使得多线程程序的并发性高。
3、另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4、线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5、从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。