永久勘误:微软等面试100题系列,答案V0.4版[第41-60题答案]

永久勘误:微软面试100系列答案V0.4[41-60题答案]

作者:July、何海涛等网友

---------------------------
几点声明:

I、 此微软面试100题系列永久更新,答案永久勘误,永久优化。
随时,永远,欢迎,任何人,针对任何一题,提出自己的思路、意见。
并对那些修正公布于博客上的答案的网友,表示最大的感谢。
II、 不管你愿不愿意相信或承认,这份微软等面试100题资料+答案系列,在整个网上,都是独一无二的,
且,它的的确确、真真实实的帮助了不下10万人。
任何人,在引用此份资料或答案,必须注明出处:http://blog.csdn.net/v_JULY_v
III、此份面试题系列暂仅限学习交流,任何组织、出版团体或个人不得私自据为己有,或侵权将其出版,违者必究。
向您的真诚,表示最大的敬意。谢谢。

全部资源,下载地址:
http://v_july_v.download.csdn.net/
100题永久维护(众网友,思路回复)地址
http://topic.csdn.net/u/20101126/10/b4f12a00-6280-492f-b785-cb6835a63dc9.html


前40题的答案,请参考这里:
答案V0.2版[第1题-20题答案]
http://blog.csdn.net/v_JULY_v/archive/2011/01/10/6126406.aspx
答案V0.3版[第21-40题答案]
http://blog.csdn.net/v_JULY_v/archive/2011/01/10/6126444.aspx
---------------------------------------------------------------------------


40、求固晶机的晶元查找程序
晶元盘由数目不详的大小一样的晶元组成,晶元并不一定全布满晶元盘,

照相机每次这能匹配一个晶元,如匹配过,则拾取该晶元,
若匹配不过,照相机则按测好的晶元间距移到下一个位置。
求遍历晶元盘的算法 求思路。

关于第41题,请看以下网友的回复:
xiuxianshen
感觉很简单啊,你对应你的元件个数新建两个相同维数的一维数组,
一组保存检测的匹配情况,一组保存该元件的距离,二维数组也可以,
遍历前先考虑数据参数就可以了。

kicming
难就难在元件的分布情况是未知的 对机器来说 它是不知道边界的 它自己不知道换行
所以很难规定换行的条件 比如从左向右找 找到某个地方发现没有元件了
那是换行还是不换行呢

换行的话,右边可能还有元件,不换行,可能当前已经到晶元盘边界了 再往右找就是地板了..
所以想求一个“盲目”遍历算法。


41.请修改append函数,利用这个函数实现:
两个非降序链表的并集,1->2->3 和 2->3->5 并为 1->2->3->5
另外只能输出结果,不能修改两个链表的数据。

此题,合并链表,要求将俩个非有序排列的链表,有顺序的合并。
如下:
//引自一网友。
#include <stdio.h>
#include <malloc.h>

typedef struct lnode {

int data;
struct lnode *next;
}lnode,*linklist;

linklist creatlist(int m)//创建链表
{

linklist p,l,s;
int i;
p=l=(linklist)malloc(sizeof(lnode));
p->next=NULL;
printf("请输入链表中的一个数字:");
scanf("%d",&p->data);
for(i=2;i<=m;i++)
{
s=(linklist)malloc(sizeof(lnode));
s->next = NULL;
printf("请输入第%d个数字",i);
scanf("%d",&s->data);
p->next=s;
p=p->next;
}
printf("\n");
return l;
}
void print(linklist h)//打印链表
{
linklist p=h->next;
int t=1;
printf("打印各个数字:\n");
do
{
printf("请输出第%d个数:",t);
printf("%d\n",p->data);
p=p->next;
t++;
}while(p);
}
linklist mergelist(void)//两个链表合并
{
int e,n;
linklist pa,pb,pc,head;
printf("请输入第一个链表的长度:");
scanf("%d",&e);
pa=creatlist(e);
printf("请输入第二个链表的长度:");
scanf("%d",&n);
pb=creatlist(n);
head=pc=(linklist)malloc(sizeof(lnode));
pc->next=NULL;
while(pa&&pb)
{
if(pa->data<=pb->data)
{
pc->next=pa;
pc=pa;
pa=pa->next;
}
else
{
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
pc->next=pa?pa:pb;
return head;
}
void main()
{
linklist head;
head=mergelist();
print(head);
}

///////////////////////////////////
请输入第一个链表的长度:5
请输入链表中的一个数字:3
请输入第2个数字2
请输入第3个数字1
请输入第4个数字7
请输入第5个数字9

请输入第二个链表的长度:5
请输入链表中的一个数字:6
请输入第2个数字4
请输入第3个数字5
请输入第4个数字8
请输入第5个数字7

打印各个数字:
请输出第1个数:3
请输出第2个数:2
请输出第3个数:1
请输出第4个数:6
请输出第5个数:4
请输出第6个数:5
请输出第7个数:7
请输出第8个数:8
请输出第9个数:7
请输出第10个数:9
Press any key to continue


//引用yangsen600。
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

struct Node
{
int num;
Node * next;
};

Node * createTail()
{
int x;
Node *head = NULL, *p = NULL, *tail = NULL;
puts("\nplease enter some digits(end of '.'):");
while( 1 == scanf("%d",&x) )
{
p = (Node *)malloc(sizeof(Node));
p->num = x;
p->next = NULL;
if( NULL == head )
{
tail = p;
head = tail;
}
else
{
tail->next = p;
tail = p;
}
}
getchar();
return head;
}

Node * CombinationNode(Node* head1, Node* head2)
{
Node *head,*tail,*p = head1,*q = head2,*s;

if( NULL == p )
return q;
if( NULL == q )
return p;

tail = p;
if( p->num > q->num)
tail = q;
head = tail;

while( NULL != p && NULL != q )
{
if(p->num <= q->num )
//如果p所指元素<q所指元素,那么把p所指元素,率先拉入合并后的链表中,
//p赋给s,并从p的下一个元素p->next查找。
//直到发现p所指 不再 < q,而是p > q了 即转至下述代码的else部分。
{
s = p;
p = p->next;
}
else
{
s = q;
q = q->next;
}
tail->next = s;
tail = s;
}

if( NULL == p )
p = q;
s = p;
tail->next = s;

return head;
}

void printHead(Node *head)
{
if( NULL == head )
return;
printf("List: ");
while(head)
{
printf("%d->",head->num);
head = head->next;
}
puts("NUL");
}

void main( void )
{
Node* head1,*head2,*head;
head1 = createTail();
printHead(head1);

head2 = createTail();
printHead(head2);

head = CombinationNode(head1,head2);
printHead(head);
}

//////////////////////////////////////////
please enter some digits(end of '.'):
3 2 1 7 9.
List: 3->2->1->7->9->NUL

please enter some digits(end of '.'):
6 4 5 8 7.
List: 6->4->5->8->7->NUL
List: 3->2->1->6->4->5->7->8->7->9->NUL
Press any key to continue
//与上述那段,输出结果一致。

已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序。
//非递归实现 链表合并排序:
Node * Merge(Node *head1 , Node *head2)
{
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1->data < head2->data )
{
head = head1 ;
p1 = head1->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}


//递归实现,
Node * MergeRecursive(Node *head1 , Node *head2)
{
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1->data < head2->data )
{
head = head1 ;
head->next = MergeRecursive(head1->next,head2);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
}


不放比较一下,这俩段核心代码,注意其区别:
Node * CombinationNode(Node* head1, Node* head2)
{
Node *head,*tail,*p = head1,*q = head2,*s;

if( NULL == p )
return q;
if( NULL == q )
return p;

tail = p;
if( p->num > q->num)
tail = q;
head = tail;

while( NULL != p && NULL != q )
{
if(p->num <= q->num )
{
s = p; //3.4
p = p->next; //
}
else
{
s = q;
q = q->next;
}
tail->next = s;
tail = s;
}

if( NULL == p )
p = q;
s = p;
tail->next = s;

return head;
}

和这段:
linklist mergelist(void)//两个链表合并
{
int e,n;
linklist pa,pb,pc,head;
printf("请输入第一个链表的长度:");
scanf("%d",&e);
pa=creatlist(e);
printf("请输入第二个链表的长度:");
scanf("%d",&n);
pb=creatlist(n);
head=pc=(linklist)malloc(sizeof(lnode)); //1.这
pc->next=NULL; //2.这
while(pa&&pb)
{
if(pa->data<=pb->data)
{
pc->next=pa; //3.这
pc=pa;
pa=pa->next;
}
else
{
pc->next=pb; //4.这
pc=pb;
pb=pb->next;
}
}
pc->next=pa?pa:pb;
return head;
}


再比较下,这俩段:
linklist mergelist(void)//两个链表合并
{
int e,n;
linklist pa,pb,pc,head;
printf("请输入第一个链表的长度:");
scanf("%d",&e);
pa=creatlist(e);
printf("请输入第二个链表的长度:");
scanf("%d",&n);
pb=creatlist(n);
head=pc=(linklist)malloc(sizeof(lnode));
pc->next=NULL;
while(pa&&pb)
{
if(pa->data<=pb->data)
{
pc->next=pa; //3
pc=pa; //1
pa=pa->next; //2
}
else
{
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
pc->next=pa?pa:pb;
return head;
}


//递归实现,
Node * MergeRecursive(Node *head1 , Node *head2)
{
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1->data < head2->data )
{
head = head1 ;
head->next = MergeRecursive(head1->next,head2);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
}

相当于,
if ( head1->data < head2->data )
{
head = head1 ; //1.head=head1;
head->next = MergeRecursive(head1->next,head2); //2.head1=head1->next;
//3.head->next=head1
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
聪明的你,相信,不要我过多解释。:)。

第43题:
43.递归和非递归俩种方法实现二叉树的前序遍历。

咱们先来复习下,基础知识。
因为关于树的遍历,在此100题中已出现过太多次了。

二叉树结点存储的数据结构:
typedef char datatype;
typedef struct node
{
datatype data;
struct node* lchild,*rchild;
} bintnode;

typedef bintnode* bintree;
bintree root;

1.树的前序遍历即:
按根 左 右 的顺序,依次
前序遍历根结点->前序遍历左子树->前序遍历右子树

前序遍历,递归算法
void preorder(bintree t)
//注,bintree为一指向二叉树根结点的指针
{
if(t)
{
printf("%c",t->data);
preorder(t->lchild);
preorder(t->rchild);
}
}


然后,依葫芦画瓢,得到....


2.中序遍历,递归算法
void preorder(bintree t)
{
if(t)
{

inorder(t->lchild);
printf("%c",t->data);
inorder(t->rchild);
}
}

3.后序遍历,递归算法
void preorder(bintree t)
{
if(t)
{

postorder(t->lchild);
postorder(t->rchild);
printf("%c",t->data);
}
}

二叉树的创建方法,
void createbintree(bintree* t)
{
char ch;
if( (ch=getchar())==' ')
*t=NULL;
else
{
*t=(bintnode*) malloc(sizeof(bintnode));
(*t)->data=ch;
createbintree(&(*t)->lchild);
createbintree(&(*t)->rchild);
}
}


接下来,
咱们在讨论二叉树遍历算法的非递归实现之前,
先看一个顺序栈的定义及其部分操作的实现
typedef struct stack
{
bintree data[100];
int tag[100];
int top;
}seqstack;

void push(seqstack* s,bintree t)
{
s->data[s->top]=t;
s->top++;
}

bintree pop(seqstack* s) //出栈
{
if(s->top!=0)
{
s->top--;
return (s->data[s->top]);
}
else
return NULL;
}


好了,现在,我们可以看二叉树前序遍历的非递归实现了。
按照二叉树前序遍历的定义,无论是访问整棵树还是其子树,均应该遵循先访问根结点,
然后访问根结点的左子树,最后访问根结点的右子树的。

因为对于一棵树(子树)t,如果t非空,访问完t的根结点值后,就应该进入t的左子树,
但此时必须将t保存起来,以便访问完其左子树后,进入其右子树的访问。
yeah,就是这个意思。:)...

即在t处设置一个回溯点,并将该回溯点进栈保存。

在整个二叉树前序遍历的过程中,程序始终要做的工作分成俩个部分:
1.当前正在处理的树(子树)
2.保存在栈中等待处理的部分。

//注:当栈中元素位于栈顶即将出栈时,意味着其根结点和左子树已访问完成,
//出栈后,进入其右子树进行访问,
//前序遍历,非递归实现
void preorderT(bintree t)
{
seqstack s;
s.top=0;
while( (t)||(s.top!=0) ) //当前处理的子树不为空或栈不为空
{
while(t) //子树不为空
{
printf("%c",t->data); //1.先访问根结点
push(&s,t); //2.访问左子树之前,记得先把根结点进栈保存
t=t->lchild; //3.然后才访问左子树,
}
if(s.top>0) //栈不为空
{
t.pop(&s); //4.pop根结点
t=t->rchild; //5.访问右子树
}
}
}


//中序遍历,非递归实现,
void inorderT(bintree t)
{
seqstack s;
s.top=0;
while( (t)||(s.top!=0) ) //当前处理的子树不为空或栈不为空
{
while(t) //子树不为空
{
push(&s,t); //1.访问左子树之前,记得先把根结点push进栈
t=t->lchild; //2.访问左子树
}
if(s.top!=0) //栈不为空
{
t.pop(&s); //3.pop根结点(访问完左子树后)
printf("%c",t->data); //4.访问根结点 (把先前保存的t给拿出来,要用了..)
t=t->rchild; //5.访问右子树
}
}
}

//后序遍历,非递归实现
后序遍历的非递归算法,稍微复杂点。请看,

按照二叉树后序遍历的定义,无论是访问整棵树还是起子树,
均应该遵循先访问根结点左子树,然后访问根结点的右子树,最后访问根结点。

值得注意的是,当一个元素位于栈顶即将处理的是,其左子树的访问一定完成,
如果其右子树不为空,接下来应该进入其右子树尽情访问。
//注意了,
但此时该栈顶元素时不能出栈的,因为它作为根结点,其本身的值还未被访问。
只有等到其右子树也访问完成后,该栈顶元素才能出栈,并输出它的值。

因此,在二叉树后序遍历的算法中,必须使用seqstack类型的数组tag,
其每个元素取值为0或1,用于标识栈中每个元素的状态。


1.当一个元素刚进栈时,其对应的tag值置为0;
2.当它位于栈顶即将被处理时,其tag值为0.意味着应该访问其右子树。
于是将右子树作为当前处理的对象,此时该栈顶元素仍应该保留在栈中。
并将其对应的tag值改为1.
3.当其右子树访问完成后,该元素又一次位于栈顶,而此时其tag值为1,
意味着其右子树已访问完成,接下来,应该直接访问的就是它,将其出栈。

void postorderT(bintree t)
{
seqstack s;
s.top=0;
while( (t)||(s.top!=0) )
{
while(t)
{
s.data[s.top]=t;
s.tag[s.top]=0; //tag置为0
s.top++;
t=t->lchild; //访问左子树
}
while( (s.top>0)&&(s.tag[s.top-1]==1) )
{
s.top--;
t=s.data[s.top];
printf("%c",t->data);
}
if(s.top>0)
{
t=s.data[s.top-1];
s.tag[s.top-1]=1;
t=t->rchild;
}
else
t=NULL;
}
}
至此,完。

44.腾讯面试题:
1.设计一个魔方(六面)的程序。
2.有一千万条短信,有重复,以文本文件的形式保存,一行一条,有重复。
请用5分钟时间,找出重复出现最多的前10条。
3.收藏了1万条url,现在给你一条url,如何找出相似的url。(面试官不解释何为相似)。

关于这题,请看众网友们给的思路或解答:
beingstudio
1、设计一个魔方(六面)的程序。
自我感觉用三维坐标描述每一个小块,对面提供旋转方法,然后每做一个变更就检测是不是成功了

2、有一千万条短信,有重复,以文本文件的形式保存,一行一条,
有重复。请用5分钟时间,找出重复出现最多的前10条。
如果是有序的
读进来就能出结果;
如果是无序的
建议采用hash或者双hash归类,如果想一次完成,还可以维护一个文件排列表

3、收藏了1万条url,现在给你一条url,如何找出相似的url。(面试官不解释何为相似)
例如 http://topic.csdn.net/u/20081029/22/c8fe34c1-25ab-4b94-986e-4c2fd4caa664.html
可以认为http://topic.csdn.net/u/20081029/22/是相似的
也就是说,我们可以认为url / 为相似的,因为一般对内容归类也会产生url前面的不同,所以 如果采用二
题的hash算法,可以稍作修改就可

jia_xiaoxin
1、设计一个魔方(六面)的程序。
可以用一个二维数组存储魔方的面,以及每一个面上的方块。

2、有一千万条短信,有重复,以文本文件的形式保存,一行一条,有重复。
请用5分钟时间,找出重复出现最多的前10条。
首先我们将文本导入数据库,使用Having子句来实现这样的功能,我们利用如下语句
select count(*) ccount from table1
group by a1 having count(*)>1
order by ccount desc
这样得到的第一个记录就是出现重复次数最多的那组数字。

yangzhongwei1031
个人觉得第二题其实完全可以导入到数据库中,然后用sql就很容易把结果查出来了。至于说一千万条查询
速度很慢的问题,这个完全是可以优化的,这也正好考察了你数据库的知识。关键是看思路,不应该把问题想死了。

第三题找相似的url,什么是相似的既然没有讲,我觉得也可以用sql来实现,把数据导入到数据库中,我们
只要按这个字排序就可以了,字符串的排序大家都知道,相同的肯定是在一起的。这样可以从首字母开始
保证最大限度的相似。

我也刚入行不久,个人想法,也许我的方法不正确,只是觉得程序员这行编码能力固然重要,
但是想法思维也要活跃些,要懂得用不同的方法去解决问题,不然真的是除了coding还是coding了。

ck4918
1,把魔方展开,得到六个正方形,定义六个结构体,内容为一个9个点和一个编号,每个点包括一个颜色标示;
在魔方展开图中根据正方形的相邻关系编号,每个正方形都有四个函数:左翻、右翻、上翻、下翻。
根据相邻关系,每个操作会引起相邻面的相关操作;比如一个面的左翻会调用右边相邻面的左翻;也就
意味着左相邻面的0、1、2三个元素与当前面互换;递归下去,直到所有面都交换完毕;

2,建立一个红黑树a;
遍历短信,对每条短信取MD5值,对每个MD5值在a中做操作:如果有值,这个key对应的值就+1,否则就=1;
遍历完后对红黑树取值最大的10个数,复杂度为10lg n

3.正则表达式,呵


zhenming_liu
1、设计一个魔方(六面)的程序。
这个就是一点简单的线性代数了。
2、有一千万条短信,有重复,以文本文件的形式保存,一行一条,有重复。
请用5分钟时间,找出重复出现最多的前10条。

同学最近写了一篇论文,好像是解这个的
http://www.cse.ust.hk/~qinzhang/papers/fp337-zhang.pdf
他的模型好像比问题复杂,但是即便是最简单的streamming模型也是这十年才有人做的。

3、收藏了1万条url,现在给你一条url,如何找出相似的url。(面试官不解释何为相似)
这些问题来来去去就是Hashing啦。如果是Hamming distance, 应该是能建造Locality sensitive hashing的(http://en.wikipedia.org/wiki/Locality_sensitive_hashing),
如果是edit distance的话,应该还是没有人建构的出来对应的Hash Function
(其实这也是把edit distance的测度空间嵌入到L_1的测度空间,我印象中好像是做不来的).

elovenana
1、设计一个魔方(六面)的程序。
typedef struct color
{
int r,g,b;
} color;
typedef struct omf
{
int 面[6],
color cl;
}mf;
2、有一千万条短信,有重复,以文本文件的形式保存,一行一条,有重复。
请用5分钟时间,找出重复出现最多的前10条。

这个可以,先取出第一条,然后,存入变量并将此条删除,与下面的比较,遇到相同的删除,
并且,计数器加一,然后,写入到另一个文件,标题和次数;重复之;直到清空文件;
然后,去比较另一个文件的次数,即可;

3、收藏了1万条url,现在给你一条url,如何找出相似的url。(面试官不解释何为相似)
现在有许多的库文件都支持,正则表达式,只要用正则去匹配就可以了;

yinghan2005
2、有一千万条短信,有重复,以文本文件的形式保存,一行一条,有重复。
请用5分钟时间,找出重复出现最多的前10条。

首先我们将文本导入数据库,使用Having子句来实现这样的功能,我们利用如下语句 select count(*)
ccount from table1 group by a1 having count(*)>1 order by ccount desc
这样得到的第一个记录就是
出现重复…

导入数据库很费时间的呀,5分钟绝对不够。

第二个问题,shell解决,不知道效率如何,
sort messages.txt |uniq -c |sort -k1 |tail -10

----------------------------------
更多,请参考:
http://topic.csdn.net/u/20081029/22/c8fe34c1-25ab-4b94-986e-4c2fd4caa664.html?11622
http://blog.csdn.net/lijiaz5033/archive/2008/11/05/3226574.aspx

还是第44题:
下文作者,lijiaz5033
第一题魔方
其实也很简单!
先说面,每面有四边,因此要建立4个句柄对应:上下左右,四个去面,就像双向链表节点有上下两面一样,
然后面里有方块矩阵,2级的数组,加完数组写个方法函数,叫旋转,参数是行列号/旋向 ,
旋向上下时行列号为行号,旋向左右时行列号为列号,意思是把某行某列往某方向旋转。

矩阵里有方块,方块有 创建六个面,按照魔方的样子,将第一面为正面,往下是底面(把第二面拿过来),
底面往下是背面,背面往下联就是上面,上面往下是正面,现在回到正面,正面往左联就是左面,
左面往左联就是后面,后面往左就是右面,右面往左是正面。。。。。。(这里不用罗索了,自己看明白了就知道怎么做了)

六个面创建完并上下左右连通后,这个程序就完成了

第2小题:
首先,一千万条短信按现在的短信长度将不会超过700M(平均情况下应该是350M),
使用内存映射文件比较合适.可以一次映射(当然如果更大的数据量的话,可以采用分段映射),
由于不需要频繁使用文件I/O和频繁分配小内存,这将大大提高了数据的加载速度.
其次,对每条短信的第i(i从0到70)个字母按ASCII码进行分组,
其实也就是创建树.i是树的深度,也是短信第i个字母.
//树结点定义
struct TNode
{
BYTE* pText;//直接指向文件映射的内存地址,使用BYTE而不用char是为符号问题
DWORD dwCount;//计算器,记录此结点的相同短信数
TNode* ChildNodes[256]; //子结点数据,由于一个字母的ASCII值不可能超过256,所以子结点也不可能超过256

TNode()
{
//初始化成员
}
~TNode()
{
//释放资源
}
};

//BYTE* pText直接指向文件映射的内存地址,使用BYTE而不用char是为符号问题
//int nIndex是字母下标
void CreateChildNode(TNode* pNode, const BYTE* pText, int nIndex)
{
if(pNode->ChildNodes[pText[nIndex]] == NULL)
{//如果不存在此子结点,就创建.TNode构造函数应该有初始化代码
//为了处理方便,这里也可以在创建的同时把此结点加到一个数组中.
pNode->ChildNodes[pText[nIndex]] = new TNode;
}

if(pText[nIndex+1] == '\0')
{//此短信已完成,记数器加1,并保存此短信内容
pNode->ChildNodes[pText[nIndex]]->dwCount++;
pNode->ChildNodes[pText[nIndex]]->pText = pText;
}
else //if(pText[nIndex] != '\0')
{//如果还未结束,就创建下一级结点
CreateNode(pNode->ChildNodes[pText[nIndex]], pText, nIndex+1);
}
}

//创建根结点,pTexts是短信数组,dwCount是短信数量(这里是一千万)
void CreateRootNode(const BYTE** pTexts, DWORD dwCount)
{
TNode RootNode;
for(DWORD dwIndex=0;dwIndex <dwCount;dwIndex++)
{
CreateNode(&RootNode, pTexts[dwIndex], 0);
}
//所有结点按dwCount的值进行排序
//代码略...

//取前10个结点,显示结果
//代码略...
}
这样处理看起来很复杂,其实就是为了减少比较次数.我认为大家看了这小段代码应该可以明白我的意思了,
其它的不多说了.
最后,就是对这些结点按dwCount的值进行排序,取前面的前10个结点就可以了.

我认为这问题只要是解决两方面的内容,一是内容加载,二是短信内容比较.
采用文件内存映射技术可以解决内容加载的性能问题(不仅仅不需要调用文件I/O函数,
而且也不需要每读出一条短信都要分配一小块内存),而使用树技术可以有效减少比较的次数.
当然基本思路是这样,如果有心情还可以在这基础上做一些优化处理,效果一定不会差的。
第3小题,略。


再看,第2小题:
2、有一千万条短信,有重复,以文本文件的形式保存,一行一条,有重复。
请用5分钟时间,找出重复出现最多的前10条。

iwillalwaysloveyou
1.短信长度是有限的,例如在中国短信长度范围为0-140字节,
2.题目中没有提到内存限制,假设内存是足够的(本题按下面算法最坏情况下需要1个多G)
2.建立140个元素的multimap数组(空短信可另行特殊处理),
下标为i的multimap与长度为i的字符串相对应。键为字符串的hash,值为字符串及其出现次数.
3.遍历短信,将短信根据长度进行处理,怎么处理我就不细说了
4.对每一个multimap,按字符串的出现次数,找出前10个字符串(也可能不足10个),
(可以用堆排序,复杂度为O(n*logn))
5.在4找出的所有字符串组成的集合中,按字符串的出现次数,找出出现最多的前10个


此题题目好像有点儿问题,次数最多的有可能不是刚好10个,例如有9个字符串各出现10次,有2个字符串出现9次,其他均小于9次。


July个人认为,建立R-B树,挺好的,如ck4918所说,
2,建立一个红黑树a;遍历短信,对每条短信取MD5值,对每个MD5值在a中做操作:
如果有值,这个key对应的值就+1,否则就=1;遍历完后对红黑树取值最大的10个数,复杂度为10*lgn。
---------------------------------------------------------------------------------------

接下来,请一次性,看第45-48题:
//注,此第45-48题的答案,仅仅只作为你思路的参考。
为了表示简明,以下我引用网友答案时,1、2、3、4、5即与以下的第45-48题,一一对应:.

雅虎:
1、对于一个整数矩阵,存在一种运算,对矩阵中任意元素加一时,需要其相邻(上下左右)
某一个元素也加一,现给出一正数矩阵,判断其是否能够由一个全零矩阵经过上述运算得到。
2、.一个整数数组,长度为n,将其分为m份,使各份的和相等,求m的最大值
比如{3,2,4,3,6} 可以分成{3,2,4,3,6} m=1;
{3,6}{2,4,3} m=2
{3,3}{2,4}{6} m=3 所以m的最大值为3

3、.搜狐:
4对括号可以有多少种匹配排列方式?比如两对括号可以有两种:()()和(())

4、创新工场:
求一个数组的最长递减子序列 比如{9,4,3,2,5,4,3,2}的
最长递减子序列为{9,5,4,3,2}

5、微软:
一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5}
是由{6,5,4,3,2,1}左移两位形成的,在这种数组中查找某一个数。

在此,只重点给出第4题,最长递减子序列的答案:
4、创新工场:最长递减子序列
求一个数组的最长递减子序列 比如{9,4,3,2,5,4,3,2}的
最长递减子序列为{9,5,4,3,2}

======================================================================
关于这5道题,看网友们给的回复[仅供参考]:
fire_woods
1. 有2个弱判断准则,不过不知道3个加起来是不是和题目等价
1) 讲矩阵分成黑白棋盘格, 所有黑色格子的数字和等于白色格子的.
2)对任意一个位置, 他的值不大于周围(上下左右)4个临格的数值的和.
2. 背包问题, 当然有些条件可以预先判断,比如(所有元素的和%m)!=0, 那么就不需要处理了.
3.1*1+3*3+2*2=14
4.直接遍历, O(n)
5.二分法查找分界点, 再二分法查找O(ln(n))。

duguyue200
1.第一反应是广搜,因为以前遇过一个这样类似的问题,应该是变式之类的。。
2.第一反应有数学方法,结合爆搜吧。一定是可以解的不过搜索量不小,可能用深搜好点。
3.第一反应是排列组合。因为规则给的很多。
4.第一反应就是动归,没说的,经典问题。
5.第一反应是因为有局部的递减性质,那就找到断点,不要恢复(因为恐怕有阴人的地方,不是一次能够恢复得了的)。局部用二分查找。

wds530405973
第2题 算法 原理的思想是将大问题转换成小问题。
就{3,2,4,3,6}的操作步骤:
第一步:想将数组递减排序得{6,4,3,3,2},求出数组中所有数的和m=18,第一个最大的数b=6, m/b=3余数为0,当除数为1,余数为0时终止。当余数不为0时,转到第三步。当余数为0时将数组划分为{6},{4,3,3,2}两个。把{4,3,3,2}看成一个新的数组。
第二步:先用{4,3,3,2}中的最大数与b=6比较,即4<b,所以再将4与最右边的数即2相加与b比较,结果相等,则将这两个数从该数组中除去生成新的数组,转到第一步,现在的结果是{6},{4,2},{3,3},把{3,3}看成一个新的数组继续重复第二步。
第三步,将数组中最大的数与最小的数取出构成一个新数组Z,剩余的构成一个数组,然后,判断m/Z中数字之和看是否余数为0,若为0,把b替换为Z中数字之和转第二步,若不为0,继续从剩余的数字中取出最小值加入到Z中,再判断m/Z中数字之和看是否余数为0,直到为0,转第二步为止。
最后得到的结果是{6},{4,2},{3,3} 这时可以计算出m为3,也可以在程序中作记载。
在第二步工程过,若出现两个数相加大于上一次的b,则将程序转到第三步。

Beiyouyu
5. 二分解决。注意到a[0] <= a[n-1],设要查找的是key、那么a[mid]、key、a[0]、a[n-1]可以确定一个顺序,将原数组切分成两部分:1部分是纯递减数组、另一部分是原问题的子问题。
具体如下:
如果a[mid] <= a[0] :那么a[0...mid]是纯递减数组;可以简单比较出key是否在a[0..mid]中,如果在那么问题就是标准的二分查找了;否则,a[mid+1...n-1]就是原问题的子问题了。
如果a[mid] >= a[n-1]: 那么a[mid...n-1]也是纯递减数组;与上类似。

Zhifeidie
第1题思路:动态规划,递归
1、先判断矩阵中的每一个值,是否可以以其为中心减1,也就是这个值和它的上下左右都大于1,如果成立,则以此值为中心包括上下左右减1,对生成的新的矩阵递归计算。退出条件:全0表示成功,如果不是全0但是所有的值都不满足前面的条件,返回失败。

第2题思路:动态规划,递归

将整个数组作为一个集合,最大的可能值就是集合的大小了,最小肯定是1,那么从2开始一次判断。如果集合可被k等分,那么首先集合的和能够被k整除,如果这个条件满足,则重复k-1次从这个集合中取出和为sum/k的子集合。
取子集合的算法是一个递归的思想,详见153楼
其他几个题目都是比较经典的问题,不赘述。


============================
以下,直到出现下一条杠杠"==========="之前,写的都是第4题。
pmars
说一下第四题:
一道做过的ACM题:
想法是弄一个数组存放已经找到的最长子序列,当然里面的元素都是找到的在各个位置上的最大的值(记得以前我发过一个这个题目的帖子)
http://topic.csdn.net/u/20100424/22/7a7b4a50-9110-4cf8-96ec-7fa728593b15.html
能做到NlogN吧!


litaoye
二部图是什么?二分图?

这个问题就是一个最大递增序列问题,最普通的想法就是用DP,n^2肯定可以(类似于LCS问题)。当然还可以优化到n*log(n),那是另一回事儿了。

litaoye
代码网上一大堆,说说思路吧,以4 2 6 3 1 5为例

逐个读入数字,

4 | 此时可能的队列长度为1,最大值为4
4 2 | 由于2 < 4,此时队列长度为1,最大值为2
4 2 6 | 6 > 2,队列有2个,一个长度为1,最大为2,一个长度为2,最大为6
4 2 6 3 | 3 < 6, 3 > 2,队列有2个,一个长度为1,最大为2,一个长度为2,最大为3
4 2 6 3 1 | 1 < 2, 1 < 3,队列有2个,一个长度为1,最大为1,一个长度为2,最大为3
4 2 6 3 1 5 | 5 > 1,5 > 3,队列有3个,一个长度为1,最大为1,一个长度为2,最大为3,一个长度为3,最大为5(分别是1 | 2,3 | 2,3,5)

走到头了,所以输出3,此时是一个最坏情况下n^2的算法,但如果每读入一个新的数时,不是逐个比较,而是利用二分法,查到小于该数的最长序列,那么就是n*log(n)的方法了。

litaoye
还是贴一段简单的代码吧,给自己留个备份,临时写的,希望没有错,C#的。
using System;
namespace csdnTest
{
class Program
{
static void Main(string[] args)
{
int[] items = new int[] { 4, 2, 6, 3, 1, 2, 5 };

//用来记录序列长队最大值的数组,index + 1就是序列的长度
int[] maxValues = new int[items.Length];
int maxLength = 1;
maxValues[0] = items[0];

for (int i = 1; i < items.Length; i++)
{
//二分查找对应的最长序列
int lengthIndex = Array.BinarySearch<int>(maxValues, 0, maxLength, items[i]);

//针对于.Net里面的BinarySearch的特殊处理,不用理会
if (lengthIndex < 0)
lengthIndex = -lengthIndex - 1;

if (lengthIndex + 1 > maxLength)
maxLength = lengthIndex + 1;

maxValues[lengthIndex] = items[i];
}

Console.WriteLine(maxLength);
}
}
}
第4题完。
======================================================

thegodofwar
大公司都爱考些“动态规划”的题,因为“动态规划”解法不定,
可能用到多种其它经典的算法(比如递归、回溯、二分、修枝......)

higherone
第一题很多人都没理解对。题目中说的是元素加一的时候,相邻的四个元素的某一个必须加一(四个中的任一个都可以),而不是四个相邻元素都加一。

感觉2楼提出的2个弱判断准则还挺有道理的。给出代码的解答我都没看,还是愿意看解题思路,动不动给代码的,多半都没搞清楚问题。
--------------------
//这里说的2楼给的2个弱判断准则,是这:-------


fire_woods
有2个弱判断准则,不过不知道3个加起来是不是和题目等价
1) 讲矩阵分成黑白棋盘格, 所有黑色格子的数字和等于白色格子的.
2)对任意一个位置, 他的值不大于周围(上下左右)4个临格的数值的和.
----------------------------------------------------------
如果这两个准则不是充要条件,那么我只想到一个回溯的方法。从某个元素开始,尝试找一个他的邻居与他成为一组,共同减一。这算一步操作。如果这样减下去,最终能达到0矩阵,则有解;否则回溯到上一步,找其它邻居成为一组,共同减一。
但是这个回溯的方法在这里有很大的弊病:回溯次数不仅跟矩阵阶数有关,还跟矩阵元素值有关,元素值越大,就越多回溯次数。所以这个算法肯定是不靠谱的。

higherone
二分最大匹配,不是求匹配的最大数目么?怎么在这个题目中应用?
另外,本题中的匹配,还不能是任意两个黑白子的匹配,而是相邻的黑白子才能匹配。是不是用起来会有问题?

接上:
litaoye
每个黑点只跟周围相邻的白点联通,白点也只跟周围相邻的黑点联通,有一个共同的源S连到黑点,每条边的容量就是黑点上面的数字,黑点同白点之间的连线,容量都看作无穷大,所有白点都连到一个共同的汇点T,权值就是白点上面的数字,如果从源S到T之间的最大流=所有黑点上面的数字和 同时=所有白点上面的数字和,那么该矩阵就是可以被还原的,以上是最大流的解法,肯定可以得出正确的结果,但至于是否为最优方法,就不一定了,这题看起来用类似贪心的思路兴许也成,不过没证明过,也没仔细想过反例,最近针对于这类黑白染色的问题,我总犯错误,只好老老实实的用最大流了。

higherone
牛!
刚看了下最大流算法的资料,理解了一下,真的是可以解决问题的。
说下我的理解,大家指正:
用黑点连线,指向相邻的白点,模拟了黑点和白点组合在一起加一的情况,并设该连线容量无穷大,是说流量在这里不是瓶颈。
源点S的流量,通过黑点,再通过白点,最后到达汇点。“最大流量=黑点之和=白点之和”,实际上是说源点到黑点的流量,最终都流到白点到汇点的连线上了,也就是黑点的每个1都找到了一个白点与之组合。
因此这个条件就等价与原题目。
那么,刚才那个最大匹配的做法不靠谱了?
=======================

lanhaibin
微软:
5.一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5}是由{6,5,4,3,2,1}左移两位形成的,在这种数组中查找某一个数。
先将数组右移恢复为原来的递减数列,然后运用二分查找即可。

jinwen0915
第三题:12种
1 () () () ()
2 (()) () ()
3 () (()) ()
4 () () (())
5 () (() ())
6 (() () ())
7 ((()) ())
8 ((()) ())
9 (() ()) ()
10((())) ()
11(()) (())
12() ((()))

lmgsdu
关于第一题,感觉类似七桥问题。个人有个解法不知道对不对。
假设正数矩阵每一个元素都是大于0的。
求矩阵每一行与列的和,满足以下两个条件即可由全零矩阵得到:
1、如果行与列的和有为奇数的,那么必须分别为偶数个。
2、在所有和为奇数的行与列中,相邻的奇数和不能为偶数对(顶角处的奇数行与奇数列不算做相邻)
还原假设条件,如果矩阵中存在0,且能够将矩阵分割成几个由正整数组成的小矩阵,则对小矩阵套用以上两个条件即可。
个人想法。

第45题至第48题完。
由于这些题的思路,都是整理各个网友的,很乱,见谅。
至于是否正确,请自己辨明。
也希望,看到此份资源的答案,如果有更好的思路、解答,请务必与我联系。

=====================================================================


49.一道看上去很吓人的算法面试题:
如何对n个数进行排序,要求时间复杂度O(n),空间复杂度O(1)
此题请看下文,作者张羿:
看上去似乎任何已知的算法都无法做到,如果谁做到了,那么所有的排序方法:QuickSort,ShellSort,HeapSort,BubbleSort等等等等,都可以扔掉了,还要这些算法干吗阿,呵呵。不过实际上,在数字范围有限制的情况下,是有一个这样的算法的,只需要用一个数组记录每个数字出现次数就可以了。
假定你的数字范围在0到65535范围之内,定义一个数组count[65536](这个空间是常量,和n无关,所以是O(1) ),初值全部为0。
那么假设有下面这些数字:
100
200
300
119
0
6
...
那么对于每个这个数字,都做在count中记录一下:
100 => count[100]++
200 => count[200]++
300 => count[300]++
119 => count[119]++
0 => count[0]++
6 => count[6]++
...
最后,遍历一边所有这些数字就可得到0~65535每个数字的个数(在count数组中),然后再顺序遍历count数组,count[n] = m,则输出m个n,(比如说有count[3] = 2, 那么说明有2个数字3),依次输出,最后可得结果。第一次遍历是O(n),第二次遍历是O(1),为常量,所以最后的时间复杂度为O(n),而空间复杂度为O(1)
这个算法很简单,相信大家都会,只是这个题太过于变态了,一般会把面试者吓住(我原来面试也出过这个题,只不过题目的表述形式要“友善”的多,呵呵)

50.网易有道笔试:
1.求一个二叉树中任意两个节点间的最大距离,两个节点的距离的定义是 这两个节点间边的个数,比如某个孩子节点和父节点间的距离是1,和相邻兄弟节点间的距离是2,优化时间空间复杂度。
2.求一个有向连通图的割点,割点的定义是,如果除去此节点和与其相关的边,有向图不再连通,描述算法。
第1小题,就是本微软等面试100题系列,第11题。
//请参考答案V0.3版。上有我给的详细思路阐述。

I am very,sorry,此刻在整理答案时,我才发现,原来这第50题与本微软等100题系列第39题重复了。
非常抱歉。望众位见谅。
此题,请参考答案V0.3版(第20-40题的答案)。


----------------------------------------------------------------
以下的10题,第51题-60题答案参考,皆整理自网友何海涛博客。
何海涛CSDN主页:http://hi.csdn.net/cadcisdhht
51.和为n连续正数序列。
题目:输入一个正数n,输出所有和为n连续正数序列。
例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5、4-6和7-8。


分析:这是网易的一道面试题。
这道题和本微软面试100题系列V0.1版的第14题有些类似。

我们可用两个数small和big分别表示序列的最小值和最大值。首先把small初始化为1,big初始化为2。
如果从small到big的序列的和大于n的话,我们向右移动small,相当于从序列中去掉较小的数字。
如果从small到big的序列的和小于n的话,我们向右移动big,相当于向序列中添加big的下一个数字。
一直到small等于(1+n)/2,因为序列至少要有两个数字。

基于这个思路,我们可以写出如下代码:
void PrintContinuousSequence(int small, int big);

// Find continuous sequence, whose sum is n
void FindContinuousSequence(int n)
{
if(n < 3)
return;

int small = 1;
int big = 2;
int middle = (1 + n) / 2;
int sum = small + big;

while(small < middle)
{
// we are lucky and find the sequence
if(sum == n)
PrintContinuousSequence(small, big);

// if the current sum is greater than n,
// move small forward
while(sum > n)
{
sum -= small;
small ++;

// we are lucky and find the sequence
if(sum == n)
PrintContinuousSequence(small, big);
}

// move big forward
big ++;
sum += big;
}
}

// Print continuous sequence between small and big
void PrintContinuousSequence(int small, int big)
{
for(int i = small; i <= big; ++ i)
printf("%d ", i);

printf("\n");
}


52.二元树的深度。
题目:输入一棵二元树的根结点,求该树的深度。
从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,
最长路径的长度为树的深度。
例如:输入二元树:
10
/ \
6 14
/ / \
4 12 16

输出该树的深度3。

二元树的结点定义如下:
struct SBinaryTreeNode // a node of the binary tree
{
int m_nValue; // value of node
SBinaryTreeNode *m_pLeft; // left child of node
SBinaryTreeNode *m_pRight; // right child of node
};
分析:这道题本质上还是考查二元树的遍历。

题目给出了一种树的深度的定义。当然,我们可以按照这种定义去得到树的所有路径,也就能得到最长路径以及它的长度。只是这种思路用来写程序有点麻烦。

我们还可以从另外一个角度来理解树的深度。如果一棵树只有一个结点,它的深度为1。
如果根结点只有左子树而没有右子树,那么树的深度应该是其左子树的深度加1;
同样如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1。
如果既有右

你可能感兴趣的:(算法,.net,面试,正则表达式,出版)