数据结构面试100题

  • 引言

     无私分享造就开源的辉煌。

     今是二零一一年十月十三日,明日14日即是本人刚好开博一周年。在一周年之际,特此分享出微软面试全部100题答案的完整版,以作为对本博客所有读者的回馈。

     一年之前的10月14日,一个名叫July (头像为手冢国光)的人在一个叫csdn的论坛上开帖分享微软等公司数据结构+算法面试100题,自此,与上千网友一起做,一起思考,一起解答这些面试题目,最终成就了一个名为:结构之法算法之道编程面试算法研究并重的博客,如今,此博客影响力逐步渗透到海外,及至到整个互联网。

     在此之前,由于本人笨拙,这微软面试100题的答案只整理到了前60题(第1-60题答案可到本人资源下载处下载:http://v_july_v.download.csdn.net/),故此,常有朋友留言或来信询问后面40题的答案。只是因个人认为:、答案只是作为一个参考,不可太过依赖;、常常因一些事情耽搁(如在整理最新的今年九月、十月份的面试题:九月腾讯,创新工场,淘宝等公司最新面试十三题十月百度,阿里巴巴,迅雷搜狗最新面试十一题);、个人正在针对那100题一题一题的写文章,多种思路,不断优化即成程序员编程艺术系列(详情,参见文末)。自此,后面40题的答案迟迟未得整理。且个人已经整理的前60题的答案,在我看来,是有诸多问题与弊端的,甚至很多答案都是错误的。

    (微软10题永久讨论地址:http://topic.csdn.net/u/20101126/10/b4f12a00-6280-492f-b785-cb6835a63dc9_9.html)

     互联网总是能给人带来惊喜。前几日,一位现居美国加州的名叫阿财的朋友发来一封邮件,并把他自己做的全部100题的答案一并发予给我,自此,便似遇见了知己。十分感谢。
     任何东西只有分享出来才更显其价值。本只需贴出后面40题的答案,因为前60题的答案本人早已整理上传至网上,但多一种思路多一种参考亦未尝不可。特此,把阿财的答案再稍加整理番,然后把全部100题的答案现今都贴出来。若有任何问题,欢迎不吝指正。谢谢。

    上千上万的人都关注过此100题,且大都都各自贡献了自己的思路,或回复于微软100题维护地址上,或回复于本博客内,人数众多,无法一一标明,特此向他们诸位表示敬意和感谢。谢谢大家,诸君的努力足以影响整个互联网,咱们已经迎来一个分享互利的新时代。

  • 微软面试100题全部答案

    更新:有朋友反应,以下的答案中思路过于简略,还是这句话,一切以程序员编程艺术系列(多种思路,多种比较,细细读之自晓其理)为准(我没怎么看阿财的这些答案,因为编程艺术系列已经说得足够清晰了。之所以把阿财的这份答案分享出来,一者,编程艺术系列目前还只写到了第二十二章,即100题之中还只详细阐述了近30道题;二者,他给的答案全部是用英文写的,这恰好方便国外的一些朋友参考;三者是为了给那一些急功近利的、浮躁的人一份速成的答案罢了)。July、二零一一年十月二十四日更新。

    当然,读者朋友有任何问题,你也可以跟阿财联系,他的邮箱地址是:kevinn9#gmail.com  (把#改成@)。

1.把二元查找树转变成排序的双向链表
题目:
输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。
要求不能创建任何新的结点,只调整指针的指向。
10
/ \
6 14
/ \ / \
4 8 12 16
转换成双向链表
4=6=8=10=12=14=16。
首先我们定义的二元查找树节点的数据结构如下:
struct BSTreeNode
{
int m_nValue; // value of node
BSTreeNode *m_pLeft; // left child of node
BSTreeNode *m_pRight; // right child of node
};
ANSWER:
This is a traditional problem that can be solved using recursion. 
For each node, connect the double linked lists created from left and right child node to form a full list.

/**
 * @param root The root node of the tree
 * @return The head node of the converted list.
 */
BSTreeNode * treeToLinkedList(BSTreeNode * root) {
  BSTreeNode * head, * tail;
  helper(head, tail, root);
  return head;
}

void helper(BSTreeNode *& head, BSTreeNode *& tail, BSTreeNode *root) {
  BSTreeNode *lt, *rh;
  if (root == NULL) {
    head = NULL, tail = NULL;
    return;
  }
  helper(head, lt, root->m_pLeft);
  helper(rh, tail, root->m_pRight);
  if (lt!=NULL) {
    lt->m_pRight = root;
    root->m_pLeft = lt;
  } else  {
    head = root;
  }
  if (rh!=NULL) {
    root->m_pRight=rh;
    rh->m_pLeft = root;
  } else {
    tail = root;
  }
}

2.设计包含min 函数的栈。
定义栈的数据结构,要求添加一个min 函数,能够得到栈的最小元素。
要求函数min、push 以及pop 的时间复杂度都是O(1)。
ANSWER:
Stack is a LIFO data structure. When some element is popped from the stack, the status will recover to the original status as before that element was pushed. So we can recover the minimum element, too.

struct MinStackElement {
  int data;
  int min;
};

struct MinStack {
  MinStackElement * data;
  int size;
  int top;
}

MinStack MinStackInit(int maxSize) {
  MinStack stack;
  stack.size = maxSize;
  stack.data = (MinStackElement*) malloc(sizeof(MinStackElement)*maxSize);
  stack.top = 0;
  return stack;
}
void MinStackFree(MinStack stack) {
  free(stack.data);
}
void MinStackPush(MinStack stack, int d) {
  if (stack.top == stack.size) error(“out of stack space.”);
  MinStackElement* p = stack.data[stack.top];
  p->data = d;
  p->min = (stack.top==0?d : stack.data[top-1]);
  if (p->min > d) p->min = d;
  top ++;
}
int MinStackPop(MinStack stack) {
  if (stack.top == 0) error(“stack is empty!”);
  return stack.data[--stack.top].data;
}
int MinStackMin(MinStack stack) {
  if (stack.top == 0) error(“stack is empty!”);
  return stack.data[stack.top-1].min;
}


3.求子数组的最大和
题目:
输入一个整形数组,数组里有正数也有负数。
数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,
因此输出为该子数组的和18。
ANSWER: 
A traditional greedy approach.
Keep current sum, slide from left to right, when sum < 0, reset sum to 0.

int maxSubarray(int a[], int size) {
  if (size<=0) error(“error array size”);
  int sum = 0;
  int max = - (1 << 31);
  int cur = 0;
  while (cur < size) {
    sum += a[cur++];
    if (sum > max) {
      max = sum;
    } else if (sum < 0) {
      sum = 0;
    }
  }
  return max;
}

4.在二元树中找出和为某一值的所有路径
题目:输入一个整数和一棵二元树。
从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。
打印出和与输入整数相等的所有路径。
例如输入整数22 和如下二元树
10
/ \
5 12
/ \
4 7
则打印出两条路径:10, 12 和10, 5, 7。
二元树节点的数据结构定义为:
struct BinaryTreeNode // a node in the binary tree
{
int m_nValue; // value of node
BinaryTreeNode *m_pLeft; // left child of node
BinaryTreeNode *m_pRight; // right child of node
};
ANSWER:
Use backtracking and recurison. We need a stack to help backtracking the path.
struct TreeNode {
  int data;
  TreeNode * left;
  TreeNode * right;
};

void printPaths(TreeNode * root, int sum) {
  int path[MAX_HEIGHT];
  helper(root, sum, path, 0);
}

void helper(TreeNode * root, int sum, int path[], int top) {
  path[top++] = root.data;
  sum -= root.data;
  if (root->left == NULL && root->right==NULL) {
    if (sum == 0) printPath(path, top);
  } else {
    if (root->left != NULL) helper(root->left, sum, path, top);
    if (root->right!=NULL) helper(root->right, sum, path, top);
  }
  top --;
  sum += root.data;    //....
}


5.查找最小的k 个元素
题目:输入n 个整数,输出其中最小的k 个。
例如输入1,2,3,4,5,6,7 和8 这8 个数字,则最小的4 个数字为1,2,3 和4。
ANSWER:
This is a very traditional question...
O(nlogn): cat I_FILE | sort -n | head -n K
O(kn): do insertion sort until k elements are retrieved.
O(n+klogn): Take O(n) time to bottom-up build a min-heap. Then sift-down k-1 times.
So traditional that I don’t want to write the codes...
Only gives the siftup and siftdown function.

/**
 *@param i the index of the element in heap a[0...n-1] to be sifted up
void siftup(int a[], int i, int n) {
  while (i>0) {
    int j=(i&1==0 ? i-1 : i+1);
    int p=(i-1)>>1;
    if (j<n && a[j]<a[i]) i = j;
    if (a[i] < a[p]) swap(a, i, p);
    i = p;
  }  
}
void siftdown(int a[], int i, int n) {  
  while (2*i+1<n){
    int l=2*i+1;
    if (l+1<n && a[l+1] < a[l]) l++;
    if (a[l] < a[i]) swap(a, i, l);
    i=l;
  }
}

第6 题
腾讯面试题:
给你10 分钟时间,根据上排给出十个数,在其下排填出对应的十个数
要求下排每个数都是先前上排那十个数在下排出现的次数。
上排的十个数如下:
【0,1,2,3,4,5,6,7,8,9】
举一个例子,
数值: 0,1,2,3,4,5,6,7,8,9
分配: 6,2,1,0,0,0,1,0,0,0
0 在下排出现了6 次,1 在下排出现了2 次,
2 在下排出现了1 次,3 在下排出现了0 次....
以此类推..
ANSWER:
I don’t like brain teasers. Will skip most of them...

第7 题
微软亚院之编程判断俩个链表是否相交
给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。
为了简化问题,我们假设俩个链表均不带环。
问题扩展:
1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一个节点列?
ANSWER:
struct Node {
  int data;
  int Node *next;
};
// if there is no cycle.
int isJoinedSimple(Node * h1, Node * h2) {
  while (h1->next != NULL) {
    h1 = h1->next;
  }
  while (h2->next != NULL) {
    h2 = h2-> next;
  }
  return h1 == h2;
}

// if there could exist cycle
int isJoined(Node *h1, Node * h2) {
  Node* cylic1 = testCylic(h1);
  Node* cylic2 = testCylic(h2);
  if (cylic1+cylic2==0) return isJoinedSimple(h1, h2);
  if (cylic1==0 && cylic2!=0 || cylic1!=0 &&cylic2==0) return 0;
  Node *p = cylic1;
  while (1) {
    if (p==cylic2 || p->next == cylic2) return 1;
    p=p->next->next;
    cylic1 = cylic1->next;
    if (p==cylic1) return 0;
  }
}

Node* testCylic(Node * h1) {
  Node * p1 = h1, *p2 = h1;
  while (p2!=NULL && p2->next!=NULL) {
    p1 = p1->next;
    p2 = p2->next->next;
    if (p1 == p2) {
      return p1;
    }
  }
  return NULL;
}

第8 题
此贴选一些比较怪的题,,由于其中题目本身与算法关系不大,仅考考思维。特此并作一题。
1.有两个房间,一间房里有三盏灯,另一间房有控制着三盏灯的三个开关,
这两个房间是分割开的,从一间里不能看到另一间的情况。
现在要求受训者分别进这两房间一次,然后判断出这三盏灯分别是由哪个开关控制的。
有什么办法呢?
ANSWER: 
Skip.

2.你让一些人为你工作了七天,你要用一根金条作为报酬。金条被分成七小块,每天给出一
块。
如果你只能将金条切割两次,你怎样分给这些工人?
ANSWER:
1+2+4;

3. ★用一种算法来颠倒一个链接表的顺序。现在在不用递归式的情况下做一遍。
ANSWER:
Node * reverse(Node * head) {
  if (head == NULL) return head;
  if (head->next == NULL) return head;
  Node * ph = reverse(head->next);
  head->next->next = head;
  head->next = NULL;
  return ph;
}
Node * reverseNonrecurisve(Node * head) {
  if (head == NULL) return head;
  Node * p = head;
  Node * previous = NULL;
  while (p->next != NULL) {
    p->next = previous;
    previous = p;
    p = p->next;
  }
  p->next = previous;
  return p;
}
★用一种算法在一个循环的链接表里插入一个节点,但不得穿越链接表。
ANSWER:
I don’t understand what is “Chuanyue”.
★用一种算法整理一个数组。你为什么选择这种方法?
ANSWER:
What is “Zhengli?”
★用一种算法使通用字符串相匹配。
ANSWER:
What is “Tongyongzifuchuan”... a string with “*” and “?”? If so, here is the code.
int match(char * str, char * ptn) {
  if (*ptn == ‘\0’) return 1;
  if (*ptn == ‘*’) {
    do {
      if (match(str++, ptn+1)) return 1;
    } while (*str != ‘\0’);
    return 0;
  }
  if (*str == ‘\0’) return 0;
  if (*str == *ptn || *ptn == ‘?’) {
    return match(str+1, ptn+1);
  }
  return 0;
}

★颠倒一个字符串。优化速度。优化空间。
void reverse(char *str) {
  reverseFixlen(str, strlen(str));
}
void reverseFixlen(char *str, int n) {
  char* p = str+n-1;
  while (str < p) {
    char c = *str;
    *str = *p; *p=c;
  }    
}
★颠倒一个句子中的词的顺序,比如将“我叫克丽丝”转换为“克丽丝叫我”,
实现速度最快,移动最少。
ANSWER: 
Reverse the whole string, then reverse each word. Using the reverseFixlen() above.
void reverseWordsInSentence(char * sen) {
  int len = strlen(sen);
  reverseFixlen(sen, len);
  char * p = str;
  while (*p!=’\0’) {
    while (*p == ‘ ‘ && *p!=’\0’) p++;
    str = p;
    while (p!= ‘ ‘ && *p!=’\0’) p++;
    reverseFixlen(str, p-str);
  }
}
★找到一个子字符串。优化速度。优化空间。
ANSWER:
KMP? BM? Sunday? Using BM or sunday, if it’s ASCII string, then it’s easy to fast access the auxiliary array. Otherwise an hashmap or bst may be needed. Lets assume it’s an ASCII string.
int bm_strstr(char *str, char *sub) {
  int len = strlen(sub);
  int i;
  int aux[256];
  memset(aux, sizeof(int), 256, len+1);
  for (i=0; i<len; i++) {
    aux[sub[i]] = len - i;
  }
  int n = strlen(str);
  i=len-1;
  while (i<n) {
    int j=i, k=len-1;
    while (k>=0 && str[j--] == sub[k--])
      ;
    if (k<0) return j+1;
    if (i+1<n)
      i+=aux[str[i+1]];
    else
      return -1;
  }
}
However, this algorithm, as well as BM, KMP algorithms use O(|sub|) space. If this is not acceptable, Rabin-carp algorithm can do it. Using hashing to fast filter out most false matchings.
#define Hbase 127
int rc_strstr(char * str, char * sub) {
  int dest= 0;
  char * p = sub;
  int len = 0;
  int TO_REDUCE = 1;
  while (*p!=’\0’) {
    dest = HBASE * dest + (int)(*p);
    TO_REDUCE *= HBASE;
    len ++;
  }
  int hash = 0;
  p = str;
  int i=0;
  while (*p != ‘\0’) {
    if (i++<len) hash = HBASE * dest + (int)(*p);
    else hash = (hash - (TO_REDUCE * (int)(*(p-len))))*HBASE + (int)(*p);
    if (hash == dest && i>=len && strncmp(sub, p-len+1, len) == 0) return i-len;
    p++;
  }
  return -1;
}
★比较两个字符串,用O(n)时间和恒量空间。
ANSWER:
What is “comparing two strings”? Just normal string comparison? The natural way use O(n) time and O(1) space.
int strcmp(char * p1, char * p2) {
  while (*p1 != ‘\0’ && *p2 != ‘\0’ && *p1 == *p2) {
    p1++, p2++;
  }
  if (*p1 == ‘\0’ && *p2 == ‘\0’) return 0;
  if (*p1 == ‘\0’) return -1;
  if (*p2 == ‘\0’) return 1;
  return (*p1 - *p2); // it can be negotiated whether the above 3 if’s are necessary, I don’t like to omit them.
}
★假设你有一个用1001 个整数组成的数组,这些整数是任意排列的,但是你知道所有的整数都在1 到1000(包括1000)之间。此外,除一个数字出现两次外,其他所有数字只出现一次。假设你只能对这个数组做一次处理,用一种算法找出重复的那个数字。如果你在运算中使用了辅助的存储方式,那么你能找到不用这种方式的算法吗?
ANSWER:
Sum up all the numbers, then subtract the sum from 1001*1002/2.
Another way, use A XOR A XOR B = B: 
int findX(int a[]) {
  int k = a[0]; 
  for (int i=1; i<=1000;i++)
    k ~= a[i]~i;
  }
  return k;
}

★不用乘法或加法增加8 倍。现在用同样的方法增加7 倍。
ANSWER:
n<<3;
(n<<3)-n;

第9 题
判断整数序列是不是二元查找树的后序遍历结果
题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。
如果是返回true,否则返回false。
例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果:
8
/ \
6 10
/ \ / \
5 7 9 11
因此返回true。
如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。
ANSWER:
This is an interesting one. There is a traditional question that requires the binary tree to be re-constructed from mid/post/pre order results. This seems similar. For the problems related to (binary) trees, recursion is the first choice. 
In this problem, we know in post-order results, the last number should be the root. So we have known the root of the BST is 8 in the example. So we can split the array by the root. 
int isPostorderResult(int a[], int n) {
  return helper(a, 0, n-1);
}
int helper(int a[], int s, int e) {
  if (e==s) return 1;
  int i=e-1;
  while (a[e]>a[i] && i>=s) i--;
  if (!helper(a, i+1, e-1))
    return 0;
  int k = l;
  while (a[e]<a[i] && i>=s) i--;
  return helper(a, s, l);
}

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

第11 题
求二叉树中节点的最大距离...
如果我们把二叉树看成一个图,父子节点之间的连线看成是双向的,
我们姑且定义"距离"为两节点之间边的个数。
写一个程序,
求一棵二叉树中相距最远的两个节点之间的距离。
ANSWER:
This is interesting... Also recursively, the longest distance between two nodes must be either from root to one leaf, or between two leafs. For the former case, it’s the tree height. For the latter case, it should be the sum of the heights of left and right subtrees of the two leaves’ most least ancestor.
The first case is also the sum the heights of subtrees, just the height + 0.

int maxDistance(Node * root) {
  int depth;
  return helper(root, depth);
}
int helper(Node * root, int &depth) {
  if (root == NULL) {
    depth = 0; return 0;
  }
  int ld, rd;
  int maxleft = helper(root->left, ld);
  int maxright = helper(root->right, rd);
  depth = max(ld, rd)+1;
  return max(maxleft, max(maxright, ld+rd));
}

第12 题
题目:求1+2+…+n,
要求不能使用乘除法、for、while、if、else、switch、case 等关键字以及条件判断语句
(A?B:C)。
ANSWER:
1+..+n=n*(n+1)/2=(n^2+n)/2
it is easy to get x/2, so the problem is to get n^2
though no if/else is allowed, we can easilly Go around using short-pass.
using macro to make it fancier:

#define  T(X, Y, i) (Y & (1<<i)) && X+=(Y<<i)

int foo(int n){
  int r=n;
  T(r, n, 0); T(r, n,1); T(r, n, 2); … T(r, n, 31);
  return r >> 1;
}

  • 23
    点赞
  • 205
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值