收藏 分享(赏)

程序员面试题精选:C++_算法_微软_google.pdf

上传人:HR专家 文档编号:6300528 上传时间:2019-04-05 格式:PDF 页数:74 大小:408.07KB
下载 相关 举报
程序员面试题精选:C++_算法_微软_google.pdf_第1页
第1页 / 共74页
程序员面试题精选:C++_算法_微软_google.pdf_第2页
第2页 / 共74页
程序员面试题精选:C++_算法_微软_google.pdf_第3页
第3页 / 共74页
程序员面试题精选:C++_算法_微软_google.pdf_第4页
第4页 / 共74页
程序员面试题精选:C++_算法_微软_google.pdf_第5页
第5页 / 共74页
点击查看更多>>
资源描述

1、程序员面试题精选 +C+算法 +微软 +google.TXT程序员面试题精选 (01)把二元查找树转变成排序的双向链表题目 :输入一棵二元查找树 ,将该二元查找树转换成一个排序的双向链表 。要求不能创建任何新的结点 ,只调整指针的指向 。比如将二元查找树10/ 6 14/ / 4 8 12 16转换成双向链表4=6=8=10=12=14=16。分析 :本题是微软的面试题 。很多与树相关的题目都是用递归的思路来解决 ,本题也不例外 。下面我们用两种不同的递归思路来分析 。思路一 :当我们到达某一结点准备调整以该结点为根结点的子树时 ,先调整其左子树将左子树转换成一个排好序的左子链表 ,再调整其右

2、子树转换右子链表 。最近链接左子链表的最右结点 (左子树的最大结点 )、 当前结点和右子链表的最左结点 (右子树的最小结点 )。 从树的根结点开始递归调整所有结点 。思路二 :我们可以中序遍历整棵树 。按照这个方式遍历树 ,比较小的结点先访问 。如果我们每访问一个结点 ,假设之前访问过的结点已经调整成一个排序双向链表 ,我们再把调整当前结点的指针将其链接到链表的末尾 。当所有结点都访问过之后 ,整棵树也就转换成一个排序双向链表了 。参考代码 :首先我们定义二元查找树结点的数据结构如下 :struct BSTreeNode / a node in the binary search treein

3、t m_nValue; / value of nodeBSTreeNode *m_pLeft; / left child of nodeBSTreeNode *m_pRight; / right child of node;思路一对应的代码 :/ Covert a sub binary-search-tree into a sorted double-linked list/ Input: pNode - the head of the sub tree/ asRight - whether pNode is the right child of its parent/ Output: if

4、asRight is true, return the least node in the sub-tree/ else return the greatest node in the sub-tree/BSTreeNode* ConvertNode(BSTreeNode* pNode, bool asRight)if(!pNode)return NULL;BSTreeNode *pLeft = NULL;BSTreeNode *pRight = NULL;/ Convert the left sub-treeif(pNode-m_pLeft)pLeft = ConvertNode(pNode

5、-m_pLeft, false);/ Connect the greatest node in the left sub-tree to the current nodeif(pLeft)pLeft-m_pRight = pNode;pNode-m_pLeft = pLeft;/ Convert the right sub-treeif(pNode-m_pRight)pRight = ConvertNode(pNode-m_pRight, true);/ Connect the least node in the right sub-tree to the current nodeif(pRi

6、ght)pNode-m_pRight = pRight;pRight-m_pLeft = pNode;BSTreeNode *pTemp = pNode;/ If the current node is the right child of its parent, / return the least node in the tree whose root is the current nodeif(asRight)while(pTemp-m_pLeft)pTemp = pTemp-m_pLeft;/ If the current node is the left child of its p

7、arent, / return the greatest node in the tree whose root is the current nodeelse第 1 页程序员面试题精选 +C+算法 +微软 +google.TXTwhile(pTemp-m_pRight)pTemp = pTemp-m_pRight;return pTemp;/ Covert a binary search tree into a sorted double-linked list/ Input: the head of tree/ Output: the head of sorted double-linke

8、d list/BSTreeNode* Convert(BSTreeNode* pHeadOfTree)/ As we want to return the head of the sorted double-linked list,/ we set the second parameter to be truereturn ConvertNode(pHeadOfTree, true);思路二对应的代码 :/ Covert a sub binary-search-tree into a sorted double-linked list/ Input: pNode - the head of t

9、he sub tree/ pLastNodeInList - the tail of the double-linked list/void ConvertNode(BSTreeNode* pNode, BSTreeNode*BSTreeNode *pCurrent = pNode;/ Convert the left sub-treeif (pCurrent-m_pLeft != NULL)ConvertNode(pCurrent-m_pLeft, pLastNodeInList);/ Put the current node into the double-linked listpCurr

10、ent-m_pLeft = pLastNodeInList; if(pLastNodeInList != NULL)pLastNodeInList-m_pRight = pCurrent;pLastNodeInList = pCurrent;/ Convert the right sub-treeif (pCurrent-m_pRight != NULL)ConvertNode(pCurrent-m_pRight, pLastNodeInList);/ Covert a binary search tree into a sorted double-linked list/ Input: pH

11、eadOfTree - the head of tree/ Output: the head of sorted double-linked list/BSTreeNode* Convert_Solution1(BSTreeNode* pHeadOfTree)BSTreeNode *pLastNodeInList = NULL;ConvertNode(pHeadOfTree, pLastNodeInList);/ Get the head of the double-linked listBSTreeNode *pHeadOfList = pLastNodeInList;while(pHead

12、OfList return pHeadOfList;程序员面试题精选 (02)设计包含 min函数的栈题目 :定义栈的数据结构 ,要求添加一个 min函数 ,能够得到栈的最小元素 。要求函数 min、push以及 pop的时间复杂度都是O(1)。 分析 :这是去年 google的一道面试题 。我看到这道题目时 ,第一反应就是每次 push一个新元素时 ,将栈里所有逆序元素排序 。这样栈顶元素将是最小元素 。但由于不能保证最后 push进栈的元素最先出栈 ,这种思路设计的数据结构已经不是一个栈了 。在栈里添加一个成员变量存放最小元素 (或最小元素的位置 )。 每次 push一个新元素进栈的时候

13、,如果该元素比当前的最小元素还要小 ,则更新最小元素 。乍一看这样思路挺好的 。但仔细一想 ,该思路存在一个重要的问题 :如果当前最小元素被 pop出去 ,如何才能得到下一个第 2 页程序员面试题精选 +C+算法 +微软 +google.TXT最小元素 ?因此仅仅只添加一个成员变量存放最小元素 (或最小元素的位置 )是不够的 。我们需要一个辅助栈 。每次 push一个新元素的时候 ,同时将最小元素 (或最小元素的位置 。考虑到栈元素的类型可能是复杂的数据结构 ,用最小元素的位置将能减少空间消耗 )push到辅助栈中 ;每次 pop一个元素出栈的时候 ,同时 pop辅助栈 。参考代码 :#inc

14、lude #include template class CStackWithMinpublic:CStackWithMin(void) virtual CStackWithMin(void) Tconst Tvoid push(const Tvoid pop(void);const Tprivate:T m_data; / the elements of stacksize_t m_minIndex; / the indices of minimum elements;/ get the last element of mutable stacktemplate T/ get the las

15、t element of non-mutable stacktemplate const T/ insert an elment at the end of stacktemplate void CStackWithMin:push(const T/ set the index of minimum elment in m_data at the end of m_minIndexif(m_minIndex.size() = 0)m_minIndex.push_back(0);elseif(value void CStackWithMin:pop()/ pop m_datam_data.pop

16、_back();/ pop m_minIndexm_minIndex.pop_back();/ get the minimum element of stacktemplate const Tassert(m_minIndex.size() 0);return m_datam_minIndex.back();举个例子演示上述代码的运行过程 :步骤 数据栈 辅助栈 最小值1.push 3 3 0 32.push 4 3,4 0,0 3第 3 页程序员面试题精选 +C+算法 +微软 +google.TXT3.push 2 3,4,2 0,0,2 24.push 1 3,4,2,1 0,0,2,3

17、15.pop 3,4,2 0,0,2 26.pop 3,4 0,0 37.push 0 3,4,0 0,0,2 0讨论 :如果思路正确 ,编写上述代码不是一件很难的事情 。但如果能注意一些细节无疑能在面试中加分 。比如我在上面的代码中做了如下的工作 :?用模板类实现 。如果别人的元素类型只是 int类型 ,模板将能给面试官带来好印象 ;?两个版本的 top函数 。在很多类中 ,都需要提供 const和非 const版本的成员访问函数 ;?min函数中 assert。把代码写的尽量安全是每个软件公司对程序员的要求 ;?添加一些注释 。注释既能提高代码的可读性 ,又能增加代码量 ,何乐而不为 ?总

18、之 ,在面试时如果时间允许 ,尽量把代码写的漂亮一些 。说不定代码中的几个小亮点就能让自己轻松拿到心仪的 Offer。程序员面试题精选 (03)求子数组的最大和题目 :输入一个整形数组 ,数组里有正数也有负数 。数组中连续的一个或多个整数组成一个子数组 ,每个子数组都有一个和 。求所有子数组的和的最大值 。要求时间复杂度为 O(n)。例如输入的数组为 1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为 3, 10, -4, 7, 2,因此输出为该子数组的和 18。分析 :本题最初为 2005年浙江大学计算机系的考研题的最后一道程序设计题 ,在2006年里包括 google在

19、内的很多知名公司都把本题当作面试题 。由于本题在网络中广为流传 ,本题也顺利成为 2006年程序员面试题中经典中的经典 。如果不考虑时间复杂度 ,我们可以枚举出所有子数组并求出他们的和 。不过非常遗憾的是 ,由于长度为 n的数组有 O(n2)个子数组 ;而且求一个长度为 n的数组的和的时间复杂度为 O(n)。因此这种思路的时间是 O(n3)。很容易理解 ,当我们加上一个正数时 ,和会增加 ;当我们加上一个负数时 ,和会减少 。如果当前得到的和是个负数 ,那么这个和在接下来的累加中应该抛弃并重新清零 ,不然的话这个负数将会减少接下来的和 。基于这样的思路 ,我们可以写出如下代码 。参考代码 :/

20、 Find the greatest sum of all sub-arrays/ Return value: if the input is valid, return true, otherwise return false/bool FindGreatestSumOfSubArray(int *pData, / an arrayunsigned int nLength, / the length of arrayint int nCurSum = nGreatestSum = 0;for(unsigned int i = 0; i nGreatestSum)nGreatestSum =

21、nCurSum;/ if all data are negative, find the greatest element in the arrayif(nGreatestSum = 0)nGreatestSum = pData0;for(unsigned int i = 1; i nGreatestSum)nGreatestSum = pData;return true; 讨论 :上述代码中有两点值得和大家讨论一下 :?函数的返回值不是子数组和的最大值 ,而是一个判断输入是否有效的标志 。如果函数返回值的是子数组和的最大值 ,那么当输入一个空指针是应该返回什么呢 ?返回 0?那这个函数的用户

22、怎么区分输入无效和子数组和的最大值刚好是 0这两中情况呢 ?基于这个考虑 ,本人认为把子数组和的最大值以引用的方式放到参数列表中 ,同时让函数返回一个函数是否正常执行的标志 。第 4 页程序员面试题精选 +C+算法 +微软 +google.TXT?输入有一类特殊情况需要特殊处理 。当输入数组中所有整数都是负数时 ,子数组和的最大值就是数组中的最大元素 。程序员面试题精选 (04)在二元树中找出和为某一值的所有路径题目 :输入一个整数和一棵二元树 。从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径 。打印出和与输入整数相等的所有路径 。例如输入整数 22和如下二元树10/ 5 1

23、2/ 4 7 则打印出两条路径 :10, 12和10, 5, 7。二元树结点的数据结构定义为 :struct BinaryTreeNode / a node in the binary treeint m_nValue; / value of nodeBinaryTreeNode *m_pLeft; / left child of nodeBinaryTreeNode *m_pRight; / right child of node;分析 :这是百度的一道笔试题 ,考查对树这种基本数据结构以及递归函数的理解 。当访问到某一结点时 ,把该结点添加到路径上 ,并累加当前结点的值 。如果当前结点为叶

24、结点并且当前路径的和刚好等于输入的整数 ,则当前的路径符合要求 ,我们把它打印出来 。如果当前结点不是叶结点 ,则继续访问它的子结点 。当前结点访问结束后 ,递归函数将自动回到父结点 。因此我们在函数退出之前要在路径上删除当前结点并减去当前结点的值,以确保返回父结点时路径刚好是根结点到父结点的路径 。我们不难看出保存路径的数据结构实际上是一个栈结构 ,因为路径要与递归调用状态一致 ,而递归调用本质就是一个压栈和出栈的过程 。参考代码 :/ Find paths whose sum equal to expected sum/void FindPath(BinaryTreeNode* pTree

25、Node, / a node of binary treeint expectedSum, / the expected sumstd:vectorcurrentSum += pTreeNode-m_nValue;path.push_back(pTreeNode-m_nValue);/ if the node is a leaf, and the sum is same as pre-defined, / the path is what we want. print the pathbool isLeaf = (!pTreeNode-m_pLeft if(currentSum = expec

26、tedSum for(; iter != path.end(); + iter)std:cout m_pLeft)FindPath(pTreeNode-m_pLeft, expectedSum, path, currentSum);if(pTreeNode-m_pRight)FindPath(pTreeNode-m_pRight, expectedSum, path, currentSum);/ when we finish visiting a node and return to its parent node,/ we should delete this node from the p

27、ath and / minus the nodes value from the current sumcurrentSum -= pTreeNode-m_nValue;path.pop_back(); 程序员面试题精选 (05)查找最小的 k个元素题目 :输入 n个整数 ,输出其中最小的 k个。例如输入 1,2,3,4,5,6,7和8这8个数字 ,则最小的 4个数字为 1,2,3和4。第 5 页程序员面试题精选 +C+算法 +微软 +google.TXT分析 :这道题最简单的思路莫过于把输入的 n个整数排序 ,这样排在最前面的 k个数就是最小的 k个数 。只是这种思路的时间复杂度为 O(nl

28、ogn)。我们试着寻找更快的解决思路 。我们可以开辟一个长度为 k的数组 。每次从输入的 n个整数中读入一个数 。如果数组中已经插入的元素少于 k个,则将读入的整数直接放到数组中 。否则长度为 k的数组已经满了 ,不能再往数组里插入元素 ,只能替换了 。如果读入的这个整数比数组中已有 k个整数的最大值要小 ,则用读入的这个整数替换这个最大值 ;如果读入的整数比数组中已有 k个整数的最大值还要大 ,则读入的这个整数不可能是最小的 k个整数之一 ,抛弃这个整数 。这种思路相当于只要排序 k个整数 ,因此时间复杂可以降到 O(n+nlogk)。通常情况下 k要远小于 n,所以这种办法要优于前面的思路

29、 。这是我能够想出来的最快的解决方案 。不过从给面试官留下更好印象的角度出发 ,我们可以进一步把代码写得更漂亮一些。从上面的分析 ,当长度为 k的数组已经满了之后 ,如果需要替换 ,每次替换的都是数组中的最大值 。在常用的数据结构中 ,能够在 O(1)时间里得到最大值的数据结构为最大堆 。因此我们可以用堆 (heap)来代替数组 。另外 ,自己重头开始写一个最大堆需要一定量的代码 。我们现在不需要重新去发明车轮 ,因为前人早就发明出来了 。同样,STL中的 set和multiset为我们做了很好的堆的实现 ,我们可以拿过来用 。既偷了懒 ,又给面试官留下熟悉 STL的好印象,何乐而不为之 ?参

30、考代码 :#include #include #include using namespace std;typedef multiset IntHeap;/ find k least numbers in a vector/void FindKLeastNumbers(const vectorif(k = 0 | data.size() :const_iterator iter = data.begin();for(; iter != data.end(); + iter)/ if less than k numbers was inserted into leastNumbersif(lea

31、stNumbers.size() root)break;/ the nodes in the right sub-tree are greater than the rootint j = i;for(; j 0)left = verifySquenceOfBST(squence, i);/ verify whether the right sub-tree is a BSTbool right = true;if(i Sum(n-1)+n; ;int solution2_Sum(int n)A a;B b;Array0 = Array1 = int value = Array1-Sum(n)

32、;return value;这种方法是用虚函数来实现函数的选择 。当n不为零时 ,执行函数 B:Sum;当n为0时,执行 A:Sum。我们也可以直接用函数指针数组 ,这样可能还更直接一些 :typedef int (*fun)(int);int solution3_f1(int i) return 0;int solution3_f2(int i)fun f2=solution3_f1, solution3_f2; return i+f!i(i-1);另外我们还可以让编译器帮我们来完成类似于递归的运算 ,比如如下代码 :template struct solution4_Sumenum Val

33、ue N = solution4_Sum:N + n;template m_pNext != NULL)pCur = pCur-m_pNext;nNum +;/ if the number of nodes in the list is less than k/ do nothingif(nNum m_pNext;return pCur;思路二的参考代码 :/ Find the kth node from the tail of a list/ Input: pListHead - the head of list/ k - the distance to the tail/ Output:

34、the kth node from the tail of a list/ListNode* FindKthToTail_Solution2(ListNode* pListHead, unsigned int k)if(pListHead = NULL)return NULL;ListNode *pAhead = pListHead;ListNode *pBehind = NULL;for(unsigned int i = 0; i m_pNext != NULL)pAhead = pAhead-m_pNext;else/ if the number of nodes in the list

35、is less than k, / do nothingreturn NULL;pBehind = pListHead;/ the distance between pAhead and pBehind is k/ when pAhead arrives at the tail, p/ Behind is at the kth node from the tail第 10 页程序员面试题精选 +C+算法 +微软 +google.TXTwhile(pAhead-m_pNext != NULL)pAhead = pAhead-m_pNext;pBehind = pBehind-m_pNext;re

36、turn pBehind;讨论 :这道题的代码有大量的指针操作 。在软件开发中 ,错误的指针操作是大部分问题的根源 。因此每个公司都希望程序员在操作指针时有良好的习惯 ,比如使用指针之前判断是不是空指针 。这些都是编程的细节 ,但如果这些细节把握得不好 ,很有可能就会和心仪的公司失之交臂 。另外 ,这两种思路对应的代码都含有循环 。含有循环的代码经常出的问题是在循环结束条件的判断 。是该用小于还是小于等于 ?是该用 k还是该用 k-1?由于题目要求的是从 0开始计数 ,而我们的习惯思维是从 1开始计数 ,因此首先要想好这些边界条件再开始编写代码 ,再者要在编写完代码之后再用边界值 、边界值减

37、1、边界值加 1都运行一次 (在纸上写代码就只能在心里运行了 )。扩展 :和这道题类似的题目还有 :输入一个单向链表 。如果该链表的结点数为奇数 ,输出中间的结点 ;如果链表结点数为偶数 ,输出中间两个结点前面的一个 。如果各位感兴趣 ,请自己分析并编写代码 。程序员面试题精选 (10)在排序数组中查找和为给定值的两个数字题目 :输入一个已经按升序排序过的数组和一个数字 ,在数组中查找两个数 ,使得它们的和正好是输入的那个数字 。要求时间复杂度是 O(n)。如果有多对数字的和等于输入的数字 ,输出任意一对即可 。例如输入数组 1、2、4、7、11、15和数字 15。由于 4+11=15,因此输

38、出 4和11。分析 :如果我们不考虑时间复杂度 ,最简单想法的莫过去先在数组中固定一个数字 ,再依次判断数组中剩下的 n-1个数字与它的和是不是等于输入的数字 。可惜这种思路需要的时间复杂度是 O(n2)。我们假设现在随便在数组中找到两个数 。如果它们的和等于输入的数字 ,那太好了 ,我们找到了要找的两个数字 ;如果小于输入的数字呢 ?我们希望两个数字的和再大一点 。由于数组已经排好序了 ,我们是不是可以把较小的数字的往后面移动一个数字 ?因为排在后面的数字要大一些 ,那么两个数字的和也要大一些 ,就有可能等于输入的数字了 ;同样 ,当两个数字的和大于输入的数字的时候 ,我们把较大的数字往前移

39、动 ,因为排在数组前面的数字要小一些 ,它们的和就有可能等于输入的数字了 。我们把前面的思路整理一下 :最初我们找到数组的第一个数字和最后一个数字 。当两个数字的和大于输入的数字时 ,把较大的数字往前移动 ;当两个数字的和小于数字时 ,把较小的数字往后移动 ;当相等时 ,打完收工 。这样扫描的顺序是从数组的两端向数组的中间扫描 。问题是这样的思路是不是正确的呢 ?这需要严格的数学证明 。感兴趣的读者可以自行证明一下 。参考代码 :/ Find two numbers with a sum in a sorted array/ Output: ture is found such two num

40、bers, otherwise false/bool FindTwoNumbersWithSum(int data, / a sorted arrayunsigned int length, / the length of the sorted array int sum, / the sumintif(length behind)long long curSum = dataahead + databehind;/ if the sum of two numbers is equal to the input/ we have found themif(curSum = sum)num1 =

41、 databehind;num2 = dataahead;found = true;break;/ if the sum of two numbers is greater than the input/ decrease the greater numberelse if(curSum sum)ahead -;第 11 页程序员面试题精选 +C+算法 +微软 +google.TXT/ if the sum of two numbers is less than the input/ increase the less numberelsebehind +;return found;扩展 :如

42、果输入的数组是没有排序的 ,但知道里面数字的范围 ,其他条件不变 ,如和在 O(n)时间里找到这两个数字 ?程序员面试题精选 (11)求二元查找树的镜像题目 :输入一颗二元查找树 ,将该树转换为它的镜像 ,即在转换后的二元查找树中 ,左子树的结点都大于右子树的结点。用递归和循环两种方法完成树的镜像转换 。 例如输入 :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

43、BSTreeNode *m_pLeft; / left child of nodeBSTreeNode *m_pRight; / right child of node;分析 :尽管我们可能一下子不能理解镜像是什么意思 ,但上面的例子给我们的直观感觉 ,就是交换结点的左右子树 。我们试着在遍历例子中的二元查找树的同时来交换每个结点的左右子树 。遍历时首先访问头结点 8,我们交换它的左右子树得到 :8/ 10 6/ /9 11 5 7我们发现两个结点 6和10的左右子树仍然是左结点的值小于右结点的值 ,我们再试着交换他们的左右子树 ,得到 :8/ 10 6/ /11 9 7 5刚好就是要求的输出 。上面的分析印证了我们的直觉 :在遍历二元查找树时每访问到一个结点 ,交换它的左右子树 。这种思路用递归不难实现,将遍历二元查找树的代码稍作修改就可以了 。参考代码如下 :/ Mirror a BST (swap the left right child of each node) recursively/ the head of BST in initial call/

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报