精选微软经典的算法面试100题(第1-20题) -代码详解(题目选自“结构之法”大侠的博客,答案都是本菜鸟自己做的)

来源:互联网 发布:mac怎么打开uvz 编辑:程序博客网 时间:2024/06/06 03:03
在此记录一下我对各个题目的代码,以作备份。本人小菜一枚,还请大家多多指教。本文章会持续更新。

1.把二元查找树转变成排序的双向链表
 题目:
输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。
要求不能创建任何新的结点,只调整指针的指向。
  
  10
  / /
 6 14
 / / / /
4 8 12 16
  
 转换成双向链表
4=6=8=10=12=14=16。

解体思路:对于每个节点,如果它有左孩子则寻找其前驱,如果有右孩子则寻找其后继。递归调用,并与其前驱后继相连

typedef struct bsnode_t{        int value;        struct bsnode_t *lchild;        struct bsnode_t *rchild;}bsnode;typedef struct bstree_t{        bsnode *root;}bstree;
void bstree_getpre(bsnode *node,bsnode **p){        if(node->lchild){                *p=node->lchild;                while((*p)->rchild)                        (*p)=(*p)->rchild;        }else{                *p=NULL;        }}void bstree_getnxt(bsnode *node,bsnode **p){        if(node->rchild){                *p=node->rchild;                while((*p)->lchild)                        (*p)=(*p)->lchild;        }else{                *p=NULL;        }}void bstree_convert(bsnode *p){        bsnode *q=NULL;        if(p->lchild){                bstree_getpre(p,&q);                bstree_convert(p->lchild);                q->rchild=p;                p->lchild=q;        }        if(p->rchild){                bstree_getnxt(p,&q);                bstree_convert(p->rchild);                p->rchild=q;                q->lchild=p;        }}void bstree_convert_to_link(bstree *tree){        bstree_convert(tree->root);        while(tree->root->lchild)                tree->root=tree->root->lchild;}

 2.设计包含min函数的栈。
定义栈的数据结构,要求添加一个min函数,能够得到栈的最小元素。
要求函数min、push以及pop的时间复杂度都是O(1)。

解题思路:栈中每个元素有一个辅助空间指明当前栈中最小值min,push操作时,比较当前栈顶元素的min与自己的大小,并将自己的min设置为较小的那个值。

typedef struct stack_elem_t{        int min;        int value;}stack_elem;typedef struct stack_t{        stack_elem *top;        stack_elem *bottom;        int length;}stack;void stack_init(stack *s,int length){        s->bottom=(stack_elem *)malloc((length+1)*sizeof(stack_elem));        memset(s->bottom,0,sizeof(s->bottom));        s->top=s->bottom;        s->length=length;}int stack_full(stack *s){        return s->top==(s->bottom+s->length);}int stack_empty(stack *s){        return s->top==s->bottom;}int push(stack *s,int value){        if(stack_full(s)){                printf("STACK FULL\n");                return -1;        }        if(stack_empty(s)){                s->top->min=value;        }else if(value>(s->top-1)->min){                s->top->min=(s->top-1)->min;        }else{                s->top->min=value;        }        (s->top++)->value=value;        return 0;}int pop(stack *s,int *value){        if(stack_empty(s)){                printf("STACK EMPTY\n");                return -1;        }        *value=(--s->top)->value;        return 0;}int min(stack *s, int *value){        if(stack_empty(s)){                printf("STACK EMPTY\n");                return -1;        }        *value=(s->top-1)->min;        return 0;}

3.求子数组的最大和
题目:
输入一个整形数组,数组里有正数也有负数。
数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
求所有子数组的和的最大值。要求时间复杂度为O(n)。

例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,
因此输出为该子数组的和18。

解体思路:遍历数组,并将每个元素逐个累加到sum中,如果sum加入某个元素后的值小于新加入的这个元素,则说明最大值路径中一定没有之前的元素,于是重新设置最大路径的起始位置,并将sum修改为当前元素。如果max_sum小于sum,则更新maz_sum并设置最大路径的终止位置为当前元素。

int get_max_sum(int *array,int count){        int i=0,max_sum=0,sum=0;        int begin=0,end=0; /*begin 为所得序列的起始下标,end所得序列的终止下标*/        for(i=0;i<count;i++){                sum+=array[i];                if(sum<array[i]){                        sum=array[i];                        begin=i;                }                if(max_sum<sum){                        max_sum=sum;                        end=i;                }        }        return max_sum;}

4.在二元树中找出和为某一值的所有路径

题目:输入一个整数和一棵二元树。
从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。
打印出和与输入整数相等的所有路径。
例如 输入整数22和如下二元树
  10  
  / /  
  5 12  
  / /  
  4 7
则打印出两条路径:10, 12和10, 5, 7

解体思路:强剪枝汇溯法。对于某个节点,如果目前为止的和已经超过了要求的整数,则不必在去检查其子节点是否适合。

void statistic_sum(bstree_elem *tree,bstree_elem **array,int index,int sum,int limit){        int i=0;        if(sum==limit){                for(i=0;i<=index;i++){                        printf("%d ",array[i]->value);                }                printf("\n");                return;        }        if(sum >limit){                return;        }        if(tree->lchild){                array[index+1]=tree->lchild;                statistic_sum(tree->lchild,array,index+1,sum+array[index+1]->value,limit);        }        if(tree->rchild){                array[index+1]=tree->rchild;                statistic_sum(tree->rchild,array,index+1,sum+array[index+1]->value,limit);        }}

 * 5.查找最小的k个元素
 * 题目:输入n个整数,输出其中最小的k个。
 * 例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。

解体思路:大顶堆求最小的k个值

void adjust_max_heap(int *array,int index, int scalar){        int i=0, s=index;        for(i=2*s;i<=scalar;i*=2){                if(i<scalar&&array[i]<array[i+1]) i++;                if(array[s]>=array[i]) break;                array[s]^=array[i];                array[i]^=array[s];                array[s]^=array[i];                s=i;        }}int creat_max_heap(int *array, int scalar){        int i=0;        for(i=scalar/2;i>0;i--){                adjust_max_heap(array,i,scalar);        }        return 0;}int heap_sort(int *array, int scalar){        int i=0;        creat_max_heap(array, scalar);        for(i=scalar;i>1;i--){                array[1]^=array[i];                array[i]^=array[1];                array[1]^=array[i];                adjust_max_heap(array,1,i-1);        }        return 0;}int main(int argc,char *argv[]){        int *array=NULL;        int scalar=0,i=0;        scanf("%d",&scalar);        array=(int *)malloc((scalar+1)*sizeof(int));        for(i=1;i<=scalar;i++)                scanf("%d",array+i);        creat_max_heap(array,scalar);        while(scanf("%d",&i)==1){                if(i<array[1]){                        array[1]=i;                        adjust_max_heap(array,1,scalar);                }        }        heap_sort(array,scalar);        for(i=1;i<=scalar;i++)                printf("%d ",array[i]);        printf("\n");        return 0;}

第6题
腾讯面试题:  
给你10分钟时间,根据上排给出十个数,在其下排填出对应的十个数  
要求下排每个数都是先前上排那十个数在下排出现的次数。  
上排的十个数如下:  
【0,1,2,3,4,5,6,7,8,9】

【6,2,1,0,0,0,1,0,0,0 】

解体思路:强剪枝回溯。将整个问题看成是10层10分支的问题树,逐个检查每个叶子节点是否符合条件。在从上至下寻找叶子节点的过程中,如果某个节点已经不符合条件,则减掉当前整个分支。

本题共有3个约束条件:

1)下排元素的和为10,即上排元素个数

2)上排元素与下排元素的向量和为10

3)强检查条件:增加辅助数组,统计下排每个元素的出现次数,然后比较辅助数组和下排数组,若有元素不同则不符合条件

void loop_fun(int *array1, int *array2, int index, int count){        int i = 0;        if (index == count) {                if (test_fun(array1, array2, count)==0) {                        print_fun(array2, count);                }                return;        }        for (i = 0; i < count; i++) {                array2[index] = i;                if (test_fun2(array2, count) == 1) {                        continue;                }                loop_fun(array1, array2, index + 1, count);        }        array2[index]=0;}int test_fun2(int *array2,int count){        int i=0;        int num_sum=0;        for(i=0;i<count;i++){                num_sum+=array2[i];        }        if(num_sum>count){                return 1;        }        return 0;}int test_fun(int *array1, int *array2, int count){        int i = 0;        int total_sum = 0;        int *array=NULL;        int flag=0;        array=(int *)malloc(count*sizeof(int));        memset(array,0,sizeof(array));        for (i = 0; i < count; i++) {                total_sum += array1[i] * array2[i];                array[array2[i]]++;        }        if (total_sum == count) {                for(i=0;i<count;i++){                        if(array[i]!=array2[i]){                                flag=1;                                break;                        }                }                if(flag==1)                        return 1;                return 0;        } else {                return 1;        }        free(array);}

第7题
微软亚院之编程判断俩个链表是否相交
给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。
为了简化问题,我们假设俩个链表均不带环。

问题扩展:
1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一个节点列?

解体思路:

1)分别判断是否有环(用两个指针,一个步长为1,一个步长为2向后移动,若两个节点重合则有环)

2)若都没有环则分别遍历两个链表,并同时记录链表的长度,然后看链表尾是否相同,

                a)若不同则不相交;

                b)若相同,将两个链表从尾部对其(长的链表从表头开始向后移动两个链表长度差个节点),然后逐个比较节点即可。

3)若一个有环一个无环,则注定不相交

3)若两个都有环,则首先在一个链表中找到出现环的那个节点,然后判断这个节点是否出现在第二个链表中。

                a)若这个节点不在第二个链表中,则两个链表不相交

                b)若这个节点在第二个链表中,这时求两个有环链表的第一个交点:

                             严格意义上讲,可以说两个有环链表的交点有两个,分别为该链表中首个踏入环的节点。

                            于是问题就转化为了:求一个有环链表中,第一个踏入环的节点。

                            在进一步:由于在3)中已经找到了一个在环中的节点,设为x,则可以将x看成一个新的链表头,

                            则问题再次转化成了已知两个链表相交,求第一个交点,只不过链表的结束条件为判断节点是否等于x

第8题

此贴选一些 比较怪的题,,由于其中题目本身与算法关系不大,仅考考思维。特此并作一题。
1.有两个房间,一间房里有三盏灯,另一间房有控制着三盏灯的三个开关,这两个房间是 分割开的,从一间里不能看到另一间的情况。
现在要求受训者分别进这两房间一次,然后判断出这三盏灯分别是由哪个开关控制的。
有什么办法呢?
答案:这个题思考了许久也没能想到答案,以下是在网上看到的答案:先打开开关A,一段时间后关闭,然后打开开关B,然后进入有灯的那个房间。亮的就是开关B控制的,在摸一下灭的两盏灯,有温度的就是开关A控制的,剩下的就是开关C控制的。

 2.你让一些人为你工作了七天,你要用一根金条作为报酬。金条被分成七小块,每天给出一块。如果你只能将金条切割两次,你怎样分给这些工人?
答案:只能切割两次的话,切成3段,分别为1/7,2/7,4/7。然后第一个给1/7,第二天给2/7然后换回前一天给的1/7,第3天给1/7,第4天给4/7然后换回之前给的1/7+2/7,第5天给1/7,第六天给2/7并换前一天给的1/7,第7天给1/7.
3.     ★链接表和数组之间的区别是什么?

  ★做一个链接表,你为什么要选择这样的方法?
  ★选择一种算法来整理出一个链接表。你为什么要选择这种方法?现在用O(n)时间来做。
  ★说说各种股票分类算法的优点和缺点。
  ★用一种算法来颠倒一个链接表的顺序。现在在不用递归式的情况下做一遍。

解体思路:顺序遍历链表,然后用头插法。

typedef struct link_elem_t{        int value;        struct link_elem_t *next;}link,*linklist;void link_reverse(linklist *l){        linklist p=NULL,s=NULL;        s=(*l)->next;        (*l)->next=NULL;        while(s){                p=s;                s=s->next;                p->next=*l;                *l=p;        }}

       ★用一种算法在一个循环的链接表里插入一个节点,但不得穿越链接表。
  ★用一种算法整理一个数组。你为什么选择这种方法?
  ★用一种算法使通用字符串相匹配。
  ★颠倒一个字符串。优化速度。优化空间。

void string_reverse(char *str){        char *p=str+strlen(str)-1;        while(str<p){                *str^=*p;                *p^=*str;                *str^=*p;                str++;                p--;        }}

★颠倒一个句子中的词的顺序,比如将“我叫克丽丝”转换为“克丽丝叫我”,实现速度最快,移动最少。

解题思路:先对整个句子执行字符串逆序,然后再对每个单词执行字符串逆序

void string_reverse(char *str,int n){        char *p=str+n-1;        while(str<p){                *str^=*p;                *p^=*str;                *str^=*p;                str++;                p--;        }}void word_reverse(char *str){        char *p=str;        int count=0;        string_reverse(str,strlen(str));        while((*p)!='\0'){                if((*p)!=' '){                        count++;                }else{                        string_reverse(p-count,count);                        count=0;                }                p++;        }}

★找到一个子字符串。优化速度。优化空间。

优化后的KMP算法

void get_next(char *str,int *next){        int len=strlen(str);        int i=0,j=-1;        next[0]=-1;        while(i<len){                if(j==-1||str[i]==str[j]){                        ++i;                        ++j;                        if(str[i]!=str[j])                                next[i]=j;                        else                                next[i]=next[j];                }else{                        j=next[j];                }        }}int find_sub_index(char *main_str,char *sub_str){        int index=0;        int *next=NULL;        int i=0,j=0;        int main_len=strlen(main_str);        int sub_len=strlen(sub_str);        next=(int *)malloc(sizeof(int)*strlen(sub_str));        get_next(sub_str,next);        while(i<main_len&&j<sub_len){                if(j==-1||main_str[i]==sub_str[j]){                        i++;                        j++;                }else{                        j=next[j];                }        }        if(j==sub_len){                index=i-sub_len;        }        free(next);        return index;}

       ★比较两个字符串,用O(n)时间和恒量空间。
  ★假设你有一个用1001个整数组成的数组,这些整数是任意排列的,但是你知道所有的整数都在1到1000(包括1000)之间。此外,除一个数字出现两次外,其他所有数字只出现一次。假设你只能对这个数组做一次处理,用一种算法找出重复的那个数字。如果你在运算中使用了辅助的存储方式,那么你能找到不用这种方式的算法吗?

解体思路:此题思考许久仍为想出不用辅助空间的方法,最后在网上看到答案,不禁感叹啊。本题有两种解法:1算数法;2异或法。

1算数法:已知1001个数,且只有一个数为重复,其余都是1到1000之间,也就是说在这1001个数里,有1000个数是不重复的,且最小为1,最大为1000,于是可以先求出这1000个数的和,然后在用1001个数的和sum-那1000个数的和,即是重复的数。sum-1000*(1+1000)/2 。

2异或法:本方法较为巧妙,且属于特定问题特定分析。我们都知道 A^B^A=B; A^A^B=B; B^A^A=B, 可见多个数进行异或运算后,与每个数出现的先后顺序无关,只与每个数出现的次数有关。于是这个问题可以转化为,通过数组中的每个数之间的异或操作,最后找出重复的那个数。到这里大家可能会想:重复的数有两个,异或以后不就是0了么,怎么能求出这个重复的数呢?其实这时,前面我说的根据特定问题特定分析就体现出来了,题目里已经限定了,前1000个数就是1~1000,所以我们就可以把刚才异或的结果在依次异或1~1000,这样,这1001个数里不重复的数都出现了两次,于是都变成了0,但是重复的那个数出现了3次,所以到最后异或的结果就是这个重复的数了。

说的挺复杂,下面直接上代码吧,看一眼就明白了:

int find_x(int *array){        int k=array[0];        int i=0;        for(i=1;i<=1000;i++){                k^=array[i]^i;        }        return k;}

★不用乘法或加法增加8倍。现在用同样的方法增加7倍。
这个题就比较简单了,增加8倍用x<<3,增加7倍用x<<3-x即可

第9题

判断整数序列是不是二元查找树的后序遍历结果
题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。
如果是返回true,否则返回false。
例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果:
  8
  / /
  6 10
  / / / /
  5 7 9 11
因此返回true。
如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。

解题思路:后续遍历序列中的最后一个元素一定是根。所以可以从数组中的最后一个元素开始,向前遍历,先找到第一个大于根的元素high,然后在找出第一个小于根的元素low。然后对low进行条件判断:每一个索引位置在low之前的元素都应该小于root。如果满足条件,再递归处理high和low两个子序列。注意:因为从根开始找Low,所以low到high之间的元素一定都是大于root的,因此无需对high子序列判断其中元素是否都大于root

int find_firstlow(int *array,int n){        int i=0;        for(i=1;i<n;i++)                if(array[-i]<array[0])                        return i;        return 0;}int find_firsthigh(int *array,int n){        int i=0;        for(i=1;i<n;i++)                if(array[-i]>array[0])                        return i;        return 0;}int little_test_fun(int *array,int low,int n){        int i=0;        for(i=low+1;i<n;i++)                if(array[-i]>array[0])                        return 1;        return 0;}int judge(int *array,int n){        int high=0,low=0;        if(n==1)                return 0;        low=find_firstlow(array,n);        high=find_firsthigh(array,n);        if(low&&little_test_fun(array,low,n))                return 1;        if(high&&judge(array-high,low>0?low-high:n-high))                return 1;        if(low&&judge(array-low,n-low))                return 1;        return 0;}

第10题
翻转句子中单词的顺序。
题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。
为简单起见,标点符号和普通字母一样处理。
例如输入“I am a student.”,则输出“student. a am I”。
与第8题重复。

第11题
求二叉树中节点的最大距离...

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

解题思路:对于二叉树这样的题,递归是很好的方法。函数返回以当前节点为根的子树的最大距离以及最大深度。在函数递归的过程中,对每个节点,分别求出其左子树以及有子树的最大距离和最大深度。则以当前节点为根的子树的最大距离为max(做子树最大深度,右子树最大深度,左子树深度+有子树深度),而当前节点的深度为max(左子树深度+右子树深度)+1

int get_max_dis(bstree_elem *tree,int *depth){        int ldep=0,rdep=0;        int ldis=0,rdis=0;        int dis=0;        if(!tree){                *depth = 0;                return 0;        }        ldis=get_max_dis(tree->lchild,&ldep);        rdis=get_max_dis(tree->rchild,&rdep);        dis=ldis>rdis?ldis:rdis;        *depth=(ldep>rdep?ldep:rdep)+1;        return dis>(ldep+rdep)?dis:(ldep+rdep);}

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



 


 

原创粉丝点击