微软等面试100题系列--(41-60)

来源:互联网 发布:淘宝上卖视频资料赚钱 编辑:程序博客网 时间:2024/05/29 14:32

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

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

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

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

参考:http://blog.csdn.net/stormbjm/article/details/8841474

44.腾讯面试题:
1.设计一个魔方(六面)的程序。

参考:http://blog.csdn.net/showman/article/details/8996
2.有一千万条短信,有重复,以文本文件的形式保存,一行一条,有重复。
请用5分钟时间,找出重复出现最多的前10条。
3.收藏了1万条url,现在给你一条url,如何找出相似的url。(面试官不解释何为相似)。

45.雅虎(运算、矩阵)

1.对于一个整数矩阵,存在一种运算,对矩阵中任意元素加一时,需要其相邻(上下左右)某一个元素也加一,现给出一正数矩阵,判断其是否能够由一个全零矩阵经过上述运算得到。

  思路:最大流。所有黑点之和与所有白点之和是相等的。每个黑点只跟周围相邻的白点联通,白点也只跟周围相邻的黑点联通,有一个共同的源S连到黑点,每条边的容量就是黑点上面的数字,黑点同白点之间的连线,容量都看作无穷大,所有白点都连到一个共同的汇点T

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

参考:http://blog.csdn.net/stormbjm/article/details/8910091

46.搜狐(运算):
四对括号可以有多少种匹配排列方式?比如两对括号可以有两种:()()和(())

卡特兰数(Catalan) 参考:http://blog.csdn.net/stormbjm/article/details/8880240

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

参考:http://blog.csdn.net/stormbjm/article/details/8919484

48.微软(运算)
一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5}
是由{6,5,4,3,2,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]也是纯递减数组;与上类似。

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.求一个有向连通图的割点,割点的定义是,如果除去此节点和与其相关的边,有向图不再连通,描述算法。

第50题与本微软等100题系列第39题重复了

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
};

int TreeDepth(SBinaryTreeNode *pTreeNode)
{
      // the depth of a empty tree is 0
      if(!pTreeNode)
            return 0;

      // the depth of left sub-tree
      int nLeft = TreeDepth(pTreeNode->m_pLeft);
      // the depth of right sub-tree
      int nRight = TreeDepth(pTreeNode->m_pRight);

      // depth is the binary tree
      return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);
}

53.字符串的全排列。
题目:输入一个字符串,打印出该字符串中字符的所有排列。
例如输入字符串abc,则输出由字符a、b、c所能排列出来的所有字符串
abc、acb、bac、bca、cab和cba。

分析:这是一道很好的考查对递归理解的编程题,
因此在过去一年中频繁出现在各大公司的面试、笔试题中。

void Permutation(char* pStr, char* pBegin)
{
      if(!pStr || !pBegin)
            return;

      // if pBegin points to the end of string,
      // this round of permutation is finished, 
      // print the permuted string
      if(*pBegin == '/0')
      {
            printf("%s/n", pStr);
      }
      // otherwise, permute string
      else
      {
            for(char* pCh = pBegin; *pCh != '/0'; ++ pCh)
            {
                  // swap pCh and pBegin
                  char temp = *pCh;
                  *pCh = *pBegin;
                  *pBegin = temp;

                  Permutation(pStr, pBegin + 1);

                  // restore pCh and pBegin
                  temp = *pCh;
                  *pCh = *pBegin;
                  *pBegin = temp;
            }
      }
}

扩展1:如果不是求字符的所有排列,而是求字符的所有组合,应该怎么办呢?
当输入的字符串中含有相同的字符串时,相同的字符交换位置是不同的排列,但是同一个组合。
举个例子,如果输入aaa,那么它的排列是6个aaa,但对应的组合只有一个。

扩展2:输入一个含有8个数字的数组,判断有没有可能把这8个数字分别放到正方体的8个顶点上,
使得正方体上三组相对的面上的4个顶点的和相等。

--------------------------------------------
后续补充:再补充说明一个全排列的程序例子:

#include<stdio.h>
#include<stdlib.h>
#define SWAP(x,y,t) ((t)=(x),(x)=(y),(y)=(t))

int score=0;
void perm(int *list,int i,int n)
{
 int j,temp;
 
 if(i==n)
 {
  for(j=0;j<=n;j++)
  {
   printf("%d",list[j]);
  }
  printf("/n");
  score++;
 }
 else
 {
  for(j=i;j<=n;j++)
  {
   SWAP(list[i],list[j],temp);
   perm(list,i+1,n);
   SWAP(list[i],list[j],temp);
  }
 }
}
void main()
{
 int list[]={1,2,3};
 perm(list,0,2);
 printf("The total number is %d/n",score);
 system("pause"); 
}

54.调整数组顺序使奇数位于偶数前面。
题目:输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分,
所有偶数位于数组的后半部分。要求时间复杂度为O(n)。

void Reorder(int *pData, unsigned int length, bool (*func)(int));
bool isEven(int n);

// Devide an array of integers into two parts, odd in the first part,
// and even in the second part
// Input: pData  - an array of integers
//        length - the length of array

void ReorderOddEven(int *pData, unsigned int length)
{
      if(pData == NULL || length == 0)
            return;

      Reorder(pData, length, isEven);
}

// Devide an array of integers into two parts, the intergers which 
// satisfy func in the first part, otherwise in the second part
// Input: pData  - an array of integers
//        length - the length of array
//        func   - a function
void Reorder(int *pData, unsigned int length, bool (*func)(int))
{
      if(pData == NULL || length == 0)
            return;

      int *pBegin = pData;
      int *pEnd = pData + length - 1;

      while(pBegin < pEnd)
      {
            // if *pBegin does not satisfy func, move forward
            if(!func(*pBegin))
            {
                  pBegin ++;
                  continue;
            }

            // if *pEnd does not satisfy func, move backward
            if(func(*pEnd))
            {
                  pEnd --;
                  continue;
            }

            // if *pBegin satisfy func while *pEnd does not,
            // swap these integers
            int temp = *pBegin;
            *pBegin = *pEnd;
            *pEnd = temp;
      }
}

// Determine whether an integer is even or not
// Input: an integer
// otherwise return false
bool isEven(int n)
{
      return (n & 1) == 0;
}

讨论:
上面的代码有三点值得提出来和大家讨论:
1.函数isEven判断一个数字是不是偶数并没有用%运算符而是用&。理由是通常情况下位运算符比%要快一些;
2.这道题有很多变种。
这里要求是把奇数放在偶数的前面,如果把要求改成:把负数放在非负数的前面等,思路都是都一样的。
3.
在函数Reorder中,用函数指针func指向的函数来判断一个数字是不是符合给定的条件,而不是用在代码直接判断(hard code)。这样的好处是把调整顺序的算法和调整的标准分开了(即解耦,decouple)。当调整的标准改变时,Reorder的代码不需要修改,只需要提供一个新的确定调整标准的函数即可,提高了代码的可维护性。

例如要求把负数放在非负数的前面,我们不需要修改Reorder的代码,只需添加一个函数来判断整数是不是非负数。
这样的思路在很多库中都有广泛的应用,比如在STL的很多算法函数中都有一个仿函数(functor)的参数(当然仿函数不是函数指针,但其思想是一样的)。
如果在面试中能够想到这一层,无疑能给面试官留下很好的印象。

55.
题目:类CMyString的声明如下:
class CMyString
{
public:
      CMyString(char* pData = NULL);
      CMyString(const CMyString& str);
      ~CMyString(void);
      CMyString& operator = (const CMyString& str);

private:
      char* m_pData;
};
请实现其赋值运算符的重载函数,要求异常安全,即当对一个对象进行赋值时发生异常,对象的状态不能改变。

分析:首先我们来看一般C++教科书上给出的赋值运算符的重载函数:
CMyString& CMyString::operator =(const CMyString &str)
{
      if(this == &str)
            return *this;

      delete []m_pData;
      m_pData = NULL;

      m_pData = new char[strlen(str.m_pData) + 1];
      strcpy(m_pData, str.m_pData);

      return *this;
}

我们知道,在分配内存时有可能发生异常。当执行语句new char[strlen(str.m_pData) + 1]发生异常时,程序将从该赋值运算符的重载函数退出不再执行。注意到这个时候语句delete []m_pData已经执行了。也就是说赋值操作没有完成,但原来对象的状态已经改变。也就是说不满足题目的异常安全的要求。

为了满足异常安全这个要求,一个简单的办法是掉换new、delete的顺序。先把内存new出来用一个临时指针保存起来,只有这个语句正常执行完成之后再执行delete。这样就能够保证异常安全了。

下面给出的是一个更加优雅的实现方案:
CMyString& CMyString::operator =(const CMyString &str)
{
      if(this != &str)
      {
            CMyString strTemp(str);

            char* pTemp = strTemp.m_pData;
            strTemp.m_pData = m_pData;
            m_pData = pTemp;
      }
      return *this;
}

该方案通过调用构造拷贝函数创建一个临时对象来分配内存。此时即使发生异常,对原来对象的状态没有影响。交换临时对象和需要赋值的对象的字符串指针之后,由于临时对象的生命周期结束,自动调用其析构函数释放需赋值对象的原来的字符串空间。整个函数不需要显式用到new、delete,内存的分配和释放都自动完成,因此代码显得比较优雅。

56.最长公共子序列。
题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,
则字符串一称之为字符串二的子串。

注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。
请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。
例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串,
则输出它们的长度4,并打印任意一个子串。

参考:http://blog.csdn.net/stormbjm/article/details/8921431

http://blog.csdn.net/v_JULY_v/archive/2010/12/31/6110269.aspx

57.用俩个栈实现队列。 
题目:某队列的声明如下:
template<typename T> class CQueue
{
public:
      CQueue() {}
      ~CQueue() {}

      void appendTail(const T& node);  // append a element to tail
      void deleteHead();               // remove a element from head

private:
     T> m_stack1;
     T> m_stack2;
};

分析:从上面的类的声明中,我们发现在队列中有两个栈。
因此这道题实质上是要求我们用两个栈来实现一个队列。
相信大家对栈和队列的基本性质都非常了解了:栈是一种后入先出的数据容器,
因此对队列进行的插入和删除操作都是在栈顶上进行;队列是一种先入先出的数据容器,
我们总是把新元素插入到队列的尾部,而从队列的头部删除元素。
参考代码如下:
// Append a element at the tail of the queue
template<typename T> void CQueue<T>::appendTail(const T& element)
{
      // push the new element into m_stack1
      m_stack1.push(element);
}

// Delete the head from the queue
template<typename T> void CQueue<T>::deleteHead()
{
      // if m_stack2 is empty, and there are some
      // elements in m_stack1, push them in m_stack2
      if(m_stack2.size() <= 0)
      {
            while(m_stack1.size() > 0)
            {
                  T& data = m_stack1.top();
                  m_stack1.pop();
                  m_stack2.push(data);
            }
      }

      // push the element into m_stack2
      assert(m_stack2.size() > 0);
      m_stack2.pop();
}

扩展:这道题是用两个栈实现一个队列。反过来能不能用两个队列实现一个栈?如果可以,该如何实现?

58.从尾到头输出链表。
题目:输入一个链表的头结点,从尾到头反过来输出每个结点的值。链表结点定义如下:
struct ListNode
{

      int       m_nKey;
      ListNode* m_pNext;
};
分析:这是一道很有意思的面试题。
该题以及它的变体经常出现在各大公司的面试、笔试题中。

思路:链表反转 或入栈 或递归

59.不能被继承的类。
题目:用C++设计一个不能被继承的类。

分析:这是Adobe公司2007年校园招聘的最新笔试题。


这道题除了考察应聘者的C++基本功底外,还能考察反应能力,是一道很好的题目。
在Java中定义了关键字final,被final修饰的类不能被继承。但在C++中没有final这个关键字,要实现这个要求还是需要花费一些精力。

 

首先想到的是在C++ 中,子类的构造函数会自动调用父类的构造函数。同样,子类的析构函数也会自动调用父类的析构函数。要想一个类不能被继承,我们只要把它的构造函数和析构函数都定义为私有函数。那么当一个类试图从它那继承的时候,必然会由于试图调用构造函数、析构函数而导致编译错误。

 

可是这个类的构造函数和析构函数都是私有函数了,我们怎样才能得到该类的实例呢?

这难不倒我们,我们可以通过定义静态来创建和释放类的实例。

 

基于这个思路,我们可以写出如下的代码:
// Define a class which can't be derived from
class FinalClass1
{
public:
      static FinalClass1* GetInstance() 
      {
            return new FinalClass1;
      }
      static void DeleteInstance( FinalClass1* pInstance)
      {
            delete pInstance;
            pInstance = 0;
      }
private:
      FinalClass1() {}
      ~FinalClass1() {}
};

 

这个类是不能被继承,但在总觉得它和一般的类有些不一样,使用起来也有点不方便。比如,我们只能得到位于堆上的实例,而得不到位于栈上实例。

 

能不能实现一个和一般类除了不能被继承之外其他用法都一样的类呢?办法总是有的,不过需要一些技巧。请看如下代码:

// Define a class which can't be derived from
template <typename T> class MakeFinal
{
      friend T;
private:
      MakeFinal() {}
      ~MakeFinal() {}
};

class FinalClass2 : virtual public MakeFinal<FinalClass2>
{
public:

      FinalClass2() {}
      ~FinalClass2() {}
};

这个类使用起来和一般的类没有区别,可以在栈上、也可以在堆上创建实例。尽管类MakeFinal<FinalClass2>的构造函数和析构函数都是私有的,但由于类FinalClass2是它的友元函数,因此在FinalClass2中调用MakeFinal<FinalClass2>的构造函数和析构函数都不会造成编译错误。

 

但当我们试图从FinalClass2继承一个类并创建它的实例时,却不同通过编译。
class Try : public FinalClass2
{
public:
      Try() {}
      ~Try() {}
};

Try temp; 
由于类FinalClass2是从类MakeFinal<FinalClass2>虚继承过来的,在调用Try的构造函数的时候,会直接跳过FinalClass2而直接调用MakeFinal<FinalClass2>的构造函数。非常遗憾的是,Try不是MakeFinal<FinalClass2>的友元,因此不能调用其私有的构造函数。

基于上面的分析,试图从FinalClass2继承的类,一旦实例化,都会导致编译错误,因此是FinalClass2不能被继承。这就满足了我们设计要求。

60.在O(1)时间内删除链表结点。
题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下:
struct ListNode

{

      int        m_nKey;

      ListNode*  m_pNext;

};


函数的声明如下:
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted);

我们可以从给定的结点得到它的下一个结点。这个时候我们实际删除的是它的下一个结点,由于我们已经得到实际删除的结点的前面一个结点,因此完全是可以实现的。当然,在删除之前,我们需要需要把给定的结点的下一个结点的数据拷贝到给定的结点中。此时,时间复杂度为O(1)。 

上面的思路还有一个问题:如果删除的结点位于链表的尾部,没有下一个结点,怎么办?我们仍然从链表的头结点开始,顺便遍历得到给定结点的前序结点,并完成删除操作。这个时候时间复杂度是O(n)。那题目要求我们需要在O(1)时间完成删除操作,我们的算法是不是不符合要求?实际上,假设链表总共有n个结点,我们的算法在n-1总情况下时间复杂度是O(1),只有当给定的结点处于链表末尾的时候,时间复杂度为O(n)。那么平均时间复杂度[(n-1)*O(1)+O(n)]/n,仍然为O(1)。

void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted)
{

  if(pToBeDeleted->m_pNext != NULL)   //要删除的结点b不是尾结点
  {
     //把b的下一个结点c先保存起来
     ListNode* pNext = pToBeDeleted->m_pNext;   //pNext指着c
  
     pToBeDeleted->m_nKey = pNext->m_nKey;   //保存起来的c的值赋给b的值
     pToBeDeleted->m_pNext = pNext->m_pNext;   //d的值赋给c
 
     //最后,删除c。
     delete pNext;
     pNext = NULL;
  }
  else
  {
     //如果b是尾结点,找头结点,只能依次遍历 找b结点了
     ListNode* pNode = pListHead;
     while(pNode->m_pNext != pToBeDeleted)
     {
        pNode = pNode->m_pNext;             
     }
     //删除c。
     pNode->m_pNext = NULL;
     delete pToBeDeleted;
     pToBeDeleted = NULL;
  }

 }