`
wcb2003
  • 浏览: 58920 次
  • 性别: Icon_minigender_1
  • 来自: zhengzhou
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

永久优化:微软技术面试100题第11-20题答案修正与优化

 
阅读更多

永久优化:微软技术面试100题第11-20题答案修正与优化


作者:July、Sorehead、leeyunce、zhedahht等。
时间:二零一一年四月四日。
微博:http://weibo.com/julyweibo
出处:http://blog.csdn.net/v_JULY_v
--------------------------------------------

引言
可能最近才看到本人博客的朋友,不了解此微软100题的具体情况。一切的详情,请参见此文:横空出世,席卷CSDN [评微软等数据结构+算法面试100题]在此,不再赘述。不过,正因为此文,曾在CSDN上刮起了一股长达4个月的微软面试题的风暴。

前言
最初的第一版答案,请参考这:永久勘误:微软等面试100题答案V0.2版[第1-20题答案]。而后对前10题第1-10题所做的勘误,见这:永久优化:微软技术面试100题第1-10题修正与优化。本文则是针对之前上传资源的第一版答案第11-20题的答案,所做的勘误,点评,修正与优化。同样,还是非常感谢网友Sorehead所辅助校正的答案。非常感谢。

当然,Sorehead所做的点评,或者他的答案肯定不是最好的,可能也有不少错误。之所以把其贴出来,是因为他发现了我之前上传答案中的很多问题与错误。相信,很多网友也发现了不少或同样的错误。为了给各位一个参考,同时也是为了相互交流,因此,成了本文。

同时,由于本人之前最初整理此100题的答案十分仓促,加之水平有限,所以肯定还存在不少问题,任何人对我之前上传的答案,如:永久勘误:微软等面试100题答案V0.2版[第1-20题答案],或者对前10题的勘误:永久优化:微软技术面试100题第1-10题修正与优化,或者对以下任何一题的答案有任何问题,都欢迎批评指正。

顺便透露下:经过一个多月的思量,本人已经决定:三年之内或之后,将出一本书。书名:不确定,内容:有关面试题还是有关算法,亦不确定。不过,本人奢望,拯救国内图书市场。

ok,扯的太多了。回到咱们眼前的旅程,yeah,现在开始。


微软技术面试100题第11-20题答案修正与优化

注意:以下内容有一部分是Sorehead在我帖子上微软100题,维护地址的回复内容(自行辨别),文中所说的楼主即指本人。且下文中所说的“楼主代码”,都是特指之前本人上传的答案第一版[第1-20题答案]读者可相互对照着阅读。
第11题(树)
求二叉树中节点的最大距离...

如果我们把二叉树看成一个图,父子节点之间的连线看成是双向的,
我们姑且定义"距离"为两节点之间边的个数。
写一个程序,
求一棵二叉树中相距最远的两个节点之间的距离。

原先我给的答案,如下(个人感觉,还是本人写的代码更清晰):

Sorehead:
十一题我写了一个非递规的方法,一方面是非递规并不复杂,另外一方面我不大喜欢楼主答案中采用全局变量的方式,我一直认为全局变量能少就少,最好不用。写的代码,如下:
一次遍历,由于必须是后序遍历,因此加了一个flag数组用于保存栈中节点的状态:0表示该节点刚开始处理;1表示其左子树遍历完毕;2表示右子树遍历完毕。当然可以创建一个结构把节点地址和这个标志放在一起。
树的前序遍历和中序遍历并不需要这个标志,所以只需要一个栈保存节点地址即可。

非递归的方法,也可以如azuryy所示,这么写:

--------------------------------------------------------
思路改进:计算一个二叉树的最大距离有两个情况:

* 情况A: 通过根节点,路径经过左子树的最深节点,再到右子树的最深节点。
* 情况B: 不通过根节点,而是左子树或右子树的最大距离路径,取其大者。

只需要计算这两个情况的路径距离,并取其大者,就是该二叉树的最大距离。

有人评价原书上的代码时,说:

书上的代码有几个缺点:
1. 算法加入侵入式(intrusive)的资料nMaxLeft, nMaxRight
2. 使用全局变量 nMaxLen。每次使用要额外初始化。而且就算是不同的独立资料,也不能在多个线程使用这个函数
3. 逻辑比较复杂,也有许多 NULL 相关的条件测试。

于是给出了下述改进代码:

第12题(语法)
题目:求1+2+…+n,
要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)。

Sorehead:说实话,我比较反感这种题目,这不是什么算法,就是一些纯技巧方面的东西,有点像我们很多考试的题目,根本就不能代表什么。
刚看到这个题目,我的第一想法就是多半只能用递规解决,接下来难点就是递规如何结束。题目的要求是比较苛刻的,楼主答案里面用的两种方法确实挺巧妙的,但我不会C++,因此只能另外想办法。

下面是我写的代码:

int sum(int n)
{
int val = 0;

n > 0 && (val = n + sum(n - 1));

return val;
}

虽然功能是实现了,但这个代码编译时是有警告的,当然这个警告不重要。但我总觉得有更加合适的方法。

或者如leeyunce所说:
思路:模板元编程,最快捷的计算方式,编译期完成计算

#include <iostream>
using namespace std;

template<int N>
struct CalCls
{
enum {sum = CalCls<N-1>::sum + N};
};

template<>
struct CalCls<0>
{
enum {sum = 0};
};

int main()
{
cout<<"1+2+3+...+100 = "<<CalCls<100>::sum<<endl;
return 0;
}

另,一位兄台,短短三行代码想解决第12题,是否正确列?:

int sum(int n)
{
return ((n * (n+1))>>1);
}


第13题(链表):
题目:输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。
链表结点定义如下:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};

思路和楼主一致,下面是我写的代码,只是判断上多一些。
list *list_find_desc(list *head, int k)
{
list *p, *q;

if (head == NULL || k < 0)
return NULL;

p = head;
while (p != NULL && k-- >= 0)
{
p = p->next;
}
if (p == NULL)
return k < 0 ? head : NULL;

q = head;
while (p != NULL)
{
p = p->next;
q = q->next;
}

return q;
}


第14题(数组):
题目:输入一个已经按升序排序过的数组和一个数字,
在数组中查找两个数,使得它们的和正好是输入的那个数字。
要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。

思路和楼主一致,这个题目比较简单,尤其是还排好序。楼主提供的另外一个思路也挺好,我还真没想到。

他说的是指以下这个思路:
2.第14题,还有一种思路,如下俩个数组:
1、 2、 4、7、11、15 //用15减一下为
14、13、11、8、4、 0 //如果下面出现了和上面一样的数,稍加判断,就能找出这俩个数来了。

第一个数组向右扫描,第二个数组向左扫描。


第15题(树):
题目:输入一颗二元查找树,将该树转换为它的镜像,
即在转换后的二元查找树中,左子树的结点都大于右子树的结点。
用递归和循环两种方法完成树的镜像转换。
例如输入:
8
/ /
6 10
// //
5 7 9 11

输出:
8
/ /
10 6
// //
11 9 7 5

定义二元查找树的结点为:
struct BSTreeNode // a node in the binary search tree (BST)
{
int m_nValue; // value of node
BSTreeNode *m_pLeft; // left child of node
BSTreeNode *m_pRight; // right child of node
};

这个题目也比较简单,对树的基本操作比较了解,很快就能写出这个代码。
但我不大推荐楼主非递规代码采用的方法,使用一个stack<list*>来实现,这种写法和递规看上去基本就是一回事,此外,这种方法是按照层次进行遍历,堆栈中要保存的数据相应也比较多,最大值是某一层最大的节点数量。

本人再多说点:

方案1,引用自网友zhedahht:
就是递归 翻转树,有子树则递归翻转子树。

//July、2010/10/19
void Revertsetree(list *root)
{
if(!root)
return;
list *p;

p=root->leftch;
root->leftch=root->rightch;
root->rightch=p; //以上三行,就是简单的交换工作。

if(root->leftch)
Revertsetree(root->leftch);
if(root->rightch)
Revertsetree(root->rightch);
}

方案2:
由于递归的本质是编译器生成了一个函数调用的栈,
因此用循环来完成同样任务时最简单的办法就是用一个辅助栈来模拟递归。

首先我们把树的头结点放入栈中。
在循环中,只要栈不为空,弹出栈的栈顶结点,交换它的左右子树。

如果它有左子树,把它的左子树压入栈中;
如果它有右子树,把它的右子树压入栈中。
这样在下次循环中就能交换它儿子结点的左右子树了。

//再用辅助栈模拟递归,改成循环的(有误之处,望不吝指正):

void Revertsetree(list *phead)
{
if(!phead)
return;

stack<list*> stacklist;
stacklist.push(phead); //首先把树的头结点放入栈中。

while(stacklist.size())
//在循环中,只要栈不为空,弹出栈的栈顶结点,交换它的左右子树
{
list* pnode=stacklist.top();
stacklist.pop();

list *ptemp;
ptemp=pnode->leftch;
pnode->leftch=pnode->rightch;
pnode->rightch=ptemp;

if(pnode->leftch)
stacklist.push(pnode->leftch); //若有左子树,把它的左子树压入栈中
if(pnode->rightch)
stacklist.push(pnode->rightch); //若有右子树,把它的右子树压入栈中
}
}


第16题(树):
题目(微软):
输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印。
例如输入

8
/ /
6 10
/ / / /
5 7 9 11

输出8 6 10 5 7 9 11。

一般遍历我们都是使用栈来实现,层次遍历显然不行,但队列先入先出正好可以用来实现这个。
下面是我写的代码:

#define QUEUE_LEN 128

typedef struct _tree
{
int key;
struct _tree *left;
struct _tree *right;
} tree;

void tree_level_traverse(tree *head)
{
tree *queue[QUEUE_LEN];
int front, rear;

if (head == NULL)
return;

front = 0;
rear = 0;
queue[rear++] = head;
while (front != rear)
{
head = queue[front];
front = (front + 1) % QUEUE_LEN;
printf("%d,", head->key);

if (head->left != NULL)
{
queue[rear] = head->left;
rear = (rear + 1) % QUEUE_LEN;
}
if (head->right != NULL)
{
queue[rear] = head->right;
rear = (rear + 1) % QUEUE_LEN;
}
}
}

第17题(字符串):
题目:在一个字符串中找到第一个只出现一次的字符。如输入abaccdeff,则输出b。
分析:这道题是2006年google的一道笔试题。

最快的方法就是采用Hash查找,基本就是采用楼主代码使用的方法。
但是楼主代码中存在一个问题,使用pHashKey作为hashTable数组下标不是很好,应该进行一下unsigned char转换。不同的编译器对char处理不同,有些默认char为有符号,有些认为是无符号,标准好像没有说明这个。如果当作有符号处理,使用负数作为数组下标显然会出现问题的。

原答案,如下(zhedahht、July):

#include <iostream.h>
#include <string.h>

char FirstNotRepeatingChar(char* pString)
{
if(!pString)
return 0;

const int tableSize = 256;
unsigned int hashTable[tableSize];
for(unsigned int i = 0; i < tableSize; ++ i)
hashTable[i] = 0;

char* pHashKey = pString;
while(*(pHashKey) != '/0')
hashTable[*(pHashKey++)] ++;

pHashKey = pString;
while(*pHashKey != '/0')
{
if(hashTable[*pHashKey] == 1)
return *pHashKey;

pHashKey++;
}
return *pHashKey;
}

int main()
{
cout<<"请输入一串字符:"<<endl;
char s[100];
cin>>s;
char* ps=s;
cout<<FirstNotRepeatingChar(ps)<<endl;
return 0;
}


第18题(数组):
题目:n个数字(0,1,…,n-1)形成一个圆圈,从数字0开始,
每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。
当一个数字删除后,从被删除数字的下一个继续删除第m个数字。
求出在这个圆圈中剩下的最后一个数字。

正如网友Wolf_killer所说,简单的约瑟夫循环问题。

说实话,这题我没想到什么好方法,只得采用笨办法来做,就和楼主答案一一样,只是我是用链表来做的。关于楼主答案二,我没想通原理是什么。

至于上面所说的思路二,是指以下这个思路:

//以下思路整理自网友zhedahht:
我们试着从数学上分析出一些规律。
首先定义最初的n个数字(0,1,…,n-1)中最后剩下的数字是关于n和m的方程为f(n,m)。

在这n个数字中,第一个被删除的数字是m%n-1,为简单起见记为k。那么删除k之后的剩下n-1的数字为0,1,…,k-1,k+1,…,n-1,并且下一个开始计数的数字是k+1。

相当于在剩下的序列中,k+1排到最前面,从而形成序列k+1,…,n-1,0,…k-1。该序列最后剩下的数字也应该是关于n和m的函数。由于这个序列的规律和前面最初的序列不一样(最初的序列是从0开始的连续序列),因此该函数不同于前面函数,记为f’(n-1,m)。

最初序列最后剩下的数字f(n,m)一定是剩下序列的最后剩下数字f’(n-1,m),所以f(n,m)=f’(n-1,m)。

接下来我们把剩下的的这n-1个数字的序列k+1,…,n-1,0,…k-1作一个映射,映射的结果是形成一个从0到n-2的序列:

k+1 -> 0
k+2 -> 1

n-1 -> n-k-2
0 -> n-k-1

k-1 -> n-2

把映射定义为p,则p(x)= (x-k-1)%n,即如果映射前的数字是x,则映射后的数字是(x-k-1)%n。对应的逆映射是p-1(x)=(x+k+1)%n。

由于映射之后的序列和最初的序列有同样的形式,都是从0开始的连续序列,因此仍然可以用函数f来表示,记为f(n-1,m)。根据我们的映射规则,映射之前的序列最后剩下的数字f’(n-1,m)= p-1 [f(n-1,m)]=[f(n-1,m)+k+1]%n。把k=m%n-1代入得到f(n,m)=f’(n-1,m)=[f(n-1,m)+m]%n。

经过上面复杂的分析,我们终于找到一个递归的公式。要得到n个数字的序列的最后剩下的数字,只需要得到n-1个数字的序列的最后剩下的数字,并可以依此类推。当n=1时,也就是序列中开始只有一个数字0,那么很显然最后剩下的数字就是0。我们把这种关系表示为:

0 n=1
f(n,m)={
[f(n-1,m)+m]%n n>1

尽管得到这个公式的分析过程非常复杂,但它用递归或者循环都很容易实现。最重要的是,这是一种时间复杂度为O(n),空间复杂度为O(1)的方法,因此无论在时间上还是空间上都优于前面的思路一。至于代码,如下:


第19题(数组、递归):
题目:定义Fibonacci数列如下:
/ 0 n=0
f(n)= 1 n=1
/ f(n-1)+f(n-2) n=2

输入n,用最快的方法求该数列的第n项。
分析:在很多C语言教科书中讲到递归函数的时候,都会用Fibonacci作为例子。
因此很多程序员对这道题的递归解法非常熟悉,但....。

我写的非递规方法和楼主的答案基本一致,下面是代码:

int fibonacci(int n)
{
int a, b, i;

if (n == 0)
return 0;
if (n == 1)
return 1;

a = 0;
b = 1;
for (i = 2; i <= n; i++)
{
b = a + b;
a = b - a;
}

return b;
}

简单的说,a、b就是用来保存f(n-1)、f(n-2)的值,a其实就是上次b的值,后来发现这个计算有点多余,每次循环其实是重复的,下面是优化后的代码,时间复杂度并没有变,但计算量减少不少:

int fibonacci2(int n)
{
int m, a, b, i;

if (n == 0)
return 0;
if (n == 1)
return 1;

m = n / 2;
a = 0;
b = 1;
for (i = 0; i < m; i++)
{
a = a + b;
b = a + b;
}

return (m * 2 == n) ? a : b;
}

我觉得肯定有比较简便的数学公式能够更加快速的计算出值来,期望楼主能说说时间复杂度为O(logn)的方法(公式比较繁琐,详情,请参见我的帖子:微软100题,维护地址第6页)。

网友onealwjwjwjwj指出:第19题 Fibonacci 数列可以用矩阵连乘得到: a[2][2]={1,1,1,0} a连乘n次,得到的a[0][1]和a[1][0]就是第n项。因为是连乘,所有可以用快速连乘(秦九韶),复杂度就可以是logN了。

第20题(字符串):
题目:输入一个表示整数的字符串,把该字符串转换成整数并输出。
例如输入字符串"345",则输出整数345。

这题目说简单也简单,不过要像楼主这样方方面面都考虑确实也不容易。
看了楼主的代码,有以下建议:
1、溢出的检查。和整型最大值做比较会永远返回假的,因为溢出后数据会自动取模。要想判断是否上溢,可以通过值是否变小了来进行判断。
2、错误处理,楼主使用g_nStatus全局变量,c语言里面用的是errno。但这种方式是存在一定问题的,个人觉得比较好的方式是函数增加一个输出参数,用来判断是否出错。
3、对于123abc这样的字符串,一般不认为是错误,而是输出123。

有任何问题,欢迎留言评论,不吝指正。也可以回复于此帖上:微软100题,维护地址。非常感谢。


版权声明:转载本BLOG内任何文章,请以超链接形式注明出处。

分享到:
评论

相关推荐

    [第二部分]精选微软等公司结构+算法面试100题[41-60题]

    3.[答案V0.2版]精选微软数据结构+算法面试100题[前20题]--修正 http://download.csdn.net/source/2813890 //此份答案是针对最初的V0.1版本,进行的校正与修正。 4.[答案V0.1版]精选微软数据结构+算法面试100题[前25...

    [最新整理公布][汇总II]微软等数据结构+算法面试100题[第1-80题]

    2.[答案V0.2 版]精选微软数据结构+算法面试100 题[前20 题]--修正 http://download.csdn.net/source/2813890 //此份答案是针对最初的V0.1 版本,进行的校正与修正。 3.[答案V0.1 版]精选微软数据结构+算法面试100 题...

    新鲜出炉:微软等数据结构+算法面试100题第81-100题[V0.1版最后20题]

    微软等数据结构+算法面试100题最后20题第81-100题新鲜出炉 ---100题系列V0.1版完整公布 作者:July 时间:2010年12月5日 ============= 首先,非常感谢各位,对本微软面试100题系列前期工作的大力支持。 很多很多...

    [汇总I]精选微软等数据结构+算法面试100题[第1-60题]

    3.[答案V0.2版]精选微软数据结构+算法面试100题[前20题]--修正 http://download.csdn.net/source/2813890 //此份答案是针对最初的V0.1版本,进行的校正与修正。 4.[答案V0.1版]精选微软数据结构+算法面试100题[前25...

    [答案V0.2版]精选微软数据结构+算法面试100题[前20题]

    精选微软等数据结构+算法面试100题答案修正V0.2版本 -------------------- 此份答案是针对,前期已公布的最初的那份答案的,初步校正与修正。 http://download.csdn.net/source/2796735(V0.1版) 相比第一份V0.1版...

    [第一部分]精选微软等公司数据结构+算法经典面试100题[1-40题]

    3.[答案V0.2版]精选微软数据结构+算法面试100题[前20题]--修正 http://download.csdn.net/source/2813890 //此份答案是针对最初的V0.1版本,进行的校正与修正。 4.[答案V0.1版]精选微软数据结构+算法面试100题[前25...

    [珍藏版]微软等数据结构+算法面试100题全部出炉[100题V0.1最终完美版]

    6.[答案V0.2版]精选微软数据结构+算法面试100题[前20题]--修正 http://download.csdn.net/source/2813890 //此份答案是针对最初的V0.1版本,进行的校正与修正。 7.[答案V0.1版]精选微软数据结构+算法面试100题[前25...

    2020年前端面试真题(阿里、网易、滴滴等)文件为百度网盘链接永久有效

    现在五块钱的付出,将来收获的可能是一份心仪的offer,干货满满,建议下载。...友情提示:本套面试题包括面试题900题+公司实战面试题400问,面试题已经整理好答案,公司题由于新收录没有答案,但非常有参考价值。

    牛客校招面试题(附答案与解析)前端.rar

    面试题库作为帮助同学准备面试的辅助资料,但是绝对不能作为备考唯一途径,因为面试是一个考察真实水平的,不是背会了答案就可以的,需要你透彻理解的,否则追问问题答不出来反而减分,毕竟技术面试中面试官最痛恨的...

    win7注册表优化加快WIN7速度

    win7注册表优化,加快WIN7速度,win7注册表优化,加快WIN7速度

    2023最新MySQL100道面试题-附答案解析

    MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。在Java企业级开发中非常常用,因为 MySQL 是开源...

    牛客校招面试题(附答案与解析)c++篇.rar

    面试题库作为帮助同学准备面试的辅助资料,但是绝对不能作为备考唯一途径,因为面试是一个考察真实水平的,不是背会了答案就可以的,需要你透彻理解的,否则追问问题答不出来反而减分,毕竟技术面试中面试官最痛恨的...

    软考网络工程师考前冲刺100题-全面精讲精炼视频.zip

    目录网盘文件永久链接 ...06 第十一章 第十二章P138-P165 07 第十二章 第十二章P165-P185 08 第十三章 第十四章P186-P218 09 第十四章 第十五章P219-P245 10 第十六章 第十七章P246-P271 11 第十八章 第十九章P272-P301

    《计算机基础与应用》第三章-计算机系统-单项选择题(含答案).docx

    《计算机基础与应用》第三章-计算机系统-单项选择题(含答案) ## # # 《计算机基础与应用》第三章-计算机系统-单项选择题(含答案)全文共19页,当前为第1页。《计算机基础与应用》第三章-计算机系统-单项选择题(含答案...

    Redis面试题50道(含答案)_.pdf

    11、Redis 集群方案什么情况下会导致整个集群不可用? 12、MySQL 里有 2000w 数据,Redis 中只存 20w 的数据, 如何保证 Redis 中的数据都是热点数据? 13、Redis 有哪些适合的场景? 14、Redis 支持的 Java 客户端...

    密评考试题+商用密码应用与安全性评估 +密码基础+刷题

    二者共享的永久密钥 D.临时密钥 结果: 错误 分数:1 难度: 解析:- 正确答案:C 3. PGP利用了4种类型的密钥:—次性会话的常规密钥、公开密钥、()和基于口令语的常规密钥。c A.私有密钥 B.对称密钥 C.非对称...

    使用Flash Cookie技术在客户端永久保存HTTP Cookie

    在我负责的一个项目中,为了实现一个特殊的需求,要求在客户端的Cookie中长久保存一份数据,但是我们知道在客户端Cookie里保存数据是不稳 定的,因为用户可能随时会清除掉浏览器的Cookie,在这种情况下,一般的解决...

    面试鸭/面试刷题/网站系统源码

    面试鸭一个干净的面试刷题网站!专业面试刷题网站,助你成为面试达人!支持自由组卷、在线刷题、校招社招斩获大厂offer,求职必备! React + 云开发 / Node.js 全栈项目,包含网站前台 + 管理员后台的完整前后端代码...

    PMP培训视频.zip

    第一章 1:前言 2:PMBOK指南概述与目标&项目 3:1.2.2项目管理&1.2.3 项目组合、项目集、项目和运营 4&5:1.2.4 指南的组成部分 第二章 6:2.1 项目所受的影响--2.4.3 管理要素 7:2.4.4 组织结构(基本)---第二章...

    软考网络考前冲刺100题.rar

    01第-章第二章p1-p29.rar 网盘文件永久链接 02第三章第四章D30-p59.ram 03第五章第五章P60-P85.rar 04第六章第章第八章P85-P8113.ram ...11第十八章第十九章P272-P301.ran 网络工程师考前中刺100题

Global site tag (gtag.js) - Google Analytics