剑指offer题目记录

1.如下为类型CMyString的声明,请为该类型添加赋值运算符函数。

1 class CMyString 
2 {
3 public:
4     CMyString(char* pData = NULL);
5     CMyString(const CMyString& str);
6     ~CMyString(void);
7 private:
8     char* m_pData;
9 };
View Code

 

2.设计一个类,我们只能生成该类的一个实例。

 

3.在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否函数该整数。

 

4.请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入"We are happy",则输出"We%20are%20happy"。

 

4_1.有两个排序的数组A1和A2,内存在A1的末尾有足够多的空余空间容纳A2。请实现一个函数,把A2中的所有数字插入到A1中并且所有的数字是排序的。

 

5.输入一个链表的头结点,从尾到头反过来打印出每个节点的值。

 

6.输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出图2.6所示的二叉树并输出它的头结点。

 

7.用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入节点和队列头部删除节点的功能。

 

7_1.用两个队列实现一个栈。

 

8.把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。

 

9.写一个函数,输入n,求斐波那契数列的第n项。

 

9_1.一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级台阶总共有多少种跳法。

 

9_2.青蛙跳台阶问题中,如果把条件改成:一只青蛙一次可以跳上1级台阶,也可以跳上2级...它也可以跳上n级,此时青蛙跳上一个n级台阶总共有多少种跳法?

 

9_3.我们可以用2*1的小矩形横着活着竖着去覆盖更大的矩形。请问8个2*1的小矩形无重叠地覆盖一个2*8的大巨星,总共有多少种方法?

 

10.请实现一个函数,输入一个整数,输出该二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。

 

10_1.用一条语句判断一个整数是不是2的整数次方。一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位是1,而其他所有位都是0。根据前面的分析,把这个整数减去1之后再和它自己做与运算,这个整数中唯一的1就会变成0。

 

10_2.输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n。比如10的二进制表示为1010,13的二进制表示为1101,需要改变1010中的3位才能得到1101 。 我们可以分为两步解决这个问题:第一步求这两个数的异或,第二步统计异或结果中1的位数。

 

10_3.把一个整数减去1之后再和原来的整数做位与运算,得到的结果相当于是把整数的二进制表示中的最右边一个1变成0 。 很多二进制的问题都可以用这个思路解决。

 

sum up

基础知识,准备这方面的知识需要从编程语言、数据结构和算法3方面做准备。通常采用概念题、代码分析和编程题3种常见题型考察对某一编程语言的掌握程度。

数据结构一直是面试考察的重点,数组和字符串是两种最基本的数据结构。链表应该是面试题中使用频率最高的一种数据结构。如果面试官想加大面试难度,他很有可能会选用树(尤其是二叉树)相关的面试题。由于栈与递归调用密切相关,队列在图(包括树)的宽度优先遍历中需要用到,因此应聘者也需要掌握这两种数据结构。

算法也是面试考察的重点,查找(特别是二分查找)和排序(特别是快速排序和归并排序)是面试中最经常考察的算法,一定要熟悉掌握。另外还需掌握分析时间复杂度的方法,理解即使是同一思路,基于循环和递归的不同实现他们的时间复杂度可能大不相同。

位运算是针对二进制数字的运算规律,熟悉掌握二进制的与、或、异或运算以及左移、右移操作,就能解决与位运算相关的问题。


 

 

11.实现函数double Power(double base,int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

(提示由于计算机表示小数float和double型都有误差,所以不能直接利用等号(==)判断两个小数是否相等。如果两个小数的绝对值很小,例如小于0.0000001,我们可以认为其相等)

 

12.输入数字n,按顺序打印出从1最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999 。

 

12_1.前面的代码中,我们都是用一个char型字符表示十进制数字的一位。8个bit的char型字符最多能表示256个字符,而十进制数字只有0-9的10个数字。因此用char型字符串来表示十进制的数字并没有充分利用内存,有一些浪费。有没有更高效的方式来表示大数。

 

12_2.定义一个函数,在该函数中可以实现任意两个整数的加法。由于没有限定输入两个数的大小范围,我们也要把它当做大数问题来处理。在前面的代码的第一个思路中,实现了在字符串表示的数字上加1的功能,我们可以参考这个思路实现两个数字相加功能,另外还有一个需要注意的问题:如果输入的数字中有负数,我们应该怎么处理?(如果面试题关于n位的整数并且没有限定n的取值范围,或者是输入任意大小的整数,那么这个题目很有可能是需要考虑大数问题的,字符串是一个简单,有效的表示大数的方法)

 

13.给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该节点。链表的节点与函数的定义如下:

1 struct ListNode
2 {
3     int m_nValue;
4     ListNode* m_pNext;
5 };
6 void DeleteNode(ListNode** pListHead,ListNode* pToBeDeleted);
View Code

(提示:删除单向链表结点的两种方法

(a)删除结点i之前,先从链表的头结点开始遍历到i前面的一个节点h,把h的m_pNext指向i的下一个节点j,再删除结点i , 这也是最经常用的一个方法,自己熟悉。

(b)把节点j的内容复制覆盖节点i,接下来再把节点i的m_pNext指向j的下一个节点之后删除结点j。这种方法不用遍历链表上结点i前面的节点,只需要有指向i结点的指针即可完成操作。

 

14.输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

(考虑可扩展性的解决方案,将判断奇偶的部分利用函数指针抽象出来达到解耦的效果)

 

15.输入一个链表,输出该链表中倒数第K个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1,2,3,4,5,6。这个链表的倒数第3个结点是值为4的结点。(注意代码鲁棒性,考虑输入空指针,链表结点总数少于k,输入的k参数为0)

 

15_1.求链表的中间结点。如果链表中结点总数为奇数,返回中间结点;如果结点总数为偶数,返回中间两个结点的任意一个。(通过一次遍历解决这个问题)

 

15_2.判断一个单项链表是否形成了环形结构。(提示:速度不同的链表指针遍历,类似于操场跑步,当我们用一个指针遍历链表不能解决问题的时候,可以尝试利用两个指针来遍历链表,可以让其中一个指针遍历的速度快一些,比如一次在链表上走两步,或者让它先在链表上走若干步

 

16.定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。

 

16_1.递归实现同样的反转链表的功能。

 

17.输入两个递增排序的链表,合并这两个链表并使新链表中结点仍然是按照递增排序的。例如输入1->3->5->7和2->4->6->8,则合并之后的升序链表应该是1->2->3->4->5->6->7->8 。

 

18.输入两颗二叉树A和B,判断B是不是A的子结构。二叉树结点的定义如下:

 

1 struct BinaryTreeNode
2 {
3     int m_nValue;
4     BinaryTreeNode* m_pLeft;
5     BinaryTreeNode* m_pRight;
6 };
View Code

 

 

sum up

 

从规范性、完整性和鲁棒性3个方面介绍了如何在面试时写出高质量的代码。

规范性:书写清晰,布局清晰,命名合理

完整性:完成基本功能,考虑边界条件,做好错误处理

鲁棒性:采取防御式编程,处理无效的输入


 

19.请完成一个函数,输入一个二叉树,该函数输出它的镜像。(递归和循环应该分别怎样实现?)

 

20.输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。例如:如果输入如下矩阵:

1    2    3    4

5    6    7    8

9    10   11   12

13   14   15   16

则依次打印出数字1、2、3、4、8、12、16、15、14、13、9、5、6、7、11、10 。

 

21.定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push以及pop的时间复杂度都是O(1)。

 

22.输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1、2、3、4、5是某栈的压栈序列,序列4、5、3、2、1是该压栈序列对应的一个弹出序列,但4、3、5、1、2就不可能是该压栈序列的弹出序列。

 

23.从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印。

 

23_1.如何广度优先遍历一个有向图?这同样也可以基于队列实现。树是图的一种特殊退化形式,从上到下按层遍历二叉树,从本质上来讲就是广度优先遍历二叉树。

 

23_2.不管是广度优先遍历一个有向图还是一棵树,都要用到队列。第一步我们把起始结点(对树而言是跟结点)放入队列中,接下来每一次从队列的头部取出一个结点,遍历这个结点之后把从它能达到的结点(对树而言是子结点)都一次放入队列。我们重复这个遍历过程,直到队列中的结点全部被遍历为止。

 

24.输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。

 

24_1.输入一个整数数组,判断该数组是不是某二叉搜索树的前序遍历的结果。这和前面的问题的后序遍历很类似,只是前序遍历的序列中,第一个数字是根结点的值。

 

24_1.如果面试题是要求处理一颗二叉树的遍历序列,我们可以先找到二叉树的根结点,再基于根结点把整棵树的遍历序列拆分成左子树对应的子序列和右子树对应的子序列,接下来再递归地处理这两个子序列。

 

25.输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

 

26.请实现函数ComplexListNode* Clone(ComplexListNode* pHead),复制一个复杂链表。在复杂链表中,每个结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任意结点或者NULL。结点C++的定义如下:

 

1 struct ComplexListNode
2 {
3     int m_nValue;
4     ComplexListNode* m_pNext;
5     COmplexListNode* m_pSibling;
6 };
View Code

 

 

27.输入一颗二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建人和新的结点,只能调整树中结点指针的指向。

 

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

 

28_1.如果不是求字符的所有排列,而是求字符的所有组合,应该怎么办?还是输入三个字符a、b、c,则它们的组合有a、b、c、ab、ac、bc、abc。当交换字符串中两个字符时,虽然能得到两个不同的排列,但却是同一个组合。比如ab和ba是不同的排列,但只算一个组合。

 

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

 

28_3.在8*8的国际象棋上摆放8个皇后,使其不能相互攻击,及任意两个皇后不得处于同一行,同一列或者同意对角线上,请问总共有多少种符合条件的摆法。

 

28_4.如果面试题是按照一定要求摆放若干数字,我们可以先求出这些数字的所有排列,然后再一一判断每个排列是不是满足题目给定的要求。

 

sum up

面试的时候难免会遇到难题,画图、举例子和分解者三种办法能够帮助解决复杂的问题。

图形能够使抽象的问题形象化,当涉及链表、二叉树等数据结构时,如果在纸上画几张草图,题目中隐藏的规律就有可能变得很直观。

一两个例子能使得抽象的问题具体化。

复杂问题分解成若干个小问题,是解决很多复杂问题的有效方法。如果我们遇到的问题很大,尝试先把大问题分解成小问题,然后递归的解决这些小问题。分治法、动态规划等方法都是基于这种思路。


 

 

29.数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

 

30.输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4 。

 

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

 

32.输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1的数字有1,10,11和12,1一共出现5次。

 

33.输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出所有数字中最小的一个。例如输入数组{3,32,321},则打印出这3个数字能排成的最小数字321323。

 

34.我们把只包含因子2,3和5的数称作丑数。求按从小到大的顺序的第1500个丑数。例如6、8都是丑数,但14不是,因为它包含因子7.习惯上我们把1当做第一个丑数。

 

35.在字符串中找出第一个只出现一次的字符。如输入"abaccdeff",则输出'b'。

 

35_1.在前面的例子中,我们之所以可以把哈希表的大小设为256,是因为字符(char)是8个bit的类型,总共只有256个字符。但实际上字符不只是256个,比如中文就有几千个汉字。如果题目要求考虑汉字,前面的算法是不是有问题?如果有,可以怎么解决。

 

35_2.定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。例如第一个字符串"we are students",第二个字符串是"aeiou",结果应该是"w r stdnts"。

 

35_3.定义一个函数,删除字符串中所有重复出现的字符。例如输入"google",则输出结果应该是"gole"。

 

35_4.请完成一个函数,判断输入的两个字符串是否是Anagram。

 

35_5.如果需要判断多个字符是不是在某个字符串里出现过或者统计多个字符在某个字符串中出现的次数,我们可以考虑基于数组创建一个简单的哈希表。这样可以用很小的空间消耗换来时间效率的提升。

 

36.在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

 

37.输入两个单向链表,找出它们的第一个公共结点。

 

sum up

降低时间复杂度的第一个方法是改用更加高效的算法。

降低时间复杂度的第二个方法是用空间换取时间,另外我们可以创建一个缓存保存中间的计算结果,从而避免重复计算。递归中会出现很多重复计算,所以这种保存已经计算的结果成为“记账法”。

空间和时间应该进行权衡,嵌入式系统的内存有限,所以不一定非要花费空闲去换取时间。

 


 

 

38.统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4。

 

39.输入一颗二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

 

39_1.输入一颗二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一颗平衡二叉树。

 

40.一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度O(1)。

 

41.输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,输出任意一对即可。

 

41_1.输入一个正数s,打印出所有和为s的连续正数序列(至少含两个数)。例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以结果打印出3个连续序列1~5,4~6和7~8。

 

42.输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am student",则输出"student. a am I"。

 

42_1.字符串的左旋转操作是把字符串前面若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如输入字符串"abcdefg"和数字2,函数将返回"cdefgab"。

 

43.把n个骰子仍在地上,所有骰子朝上一面的点数之和为s,输入n,打印出s的所有可能的值出现的概率。

 

44.从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王可以看成任意数字。

 

45.0~n-1这n个数字排列成一个圆圈,从数字0开始每次从这个圆圈中删除第m个数字。求出这个圆圈里剩下的最后一个数字。

 

46.求1+2+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句。

 

47.写一个函数,求两个整数之和,要求函数体内部的使用+、-、*、\四则与水暖符号。

 

48.用C++设计一个不能被继承的类。

 

sum up

沟通能力、学习能力。善于提问的人有较好的沟通和学习能力。

知识迁移能力能帮助我们轻松解决很多问题,举一反三的能力,平时要有一定的积累,每完成一道题目之后都要总结解题方法。

抽象建模能力,选取适当的数据结构表述模型,分析模型中的内在规律确定计算方法。

发散思维能力,跳出常规思路的束缚,从不同的角度去尝试新的方法。

 

 


 

 

 

posted @ 2013-09-22 20:50  weixliu  阅读(3905)  评论(0编辑  收藏