我所理解的要点和难点——剑指offer简要笔记

来源:互联网 发布:java免费下载 编辑:程序博客网 时间:2024/06/10 09:11

1、Singleton模式。设计一个类,只能生成该类的一个实例

 

考虑多线程环境,加同步锁。

 

2、替换空格。把字符串中每个空格换成“%20”

 

从字符串的后面开始复制和替换,准备两个指针,P1指向原始字符串末尾,P2指向替换之后的字符串末尾。

 

3、从尾到头打印链表

 

方法1:栈  方法2:递归

 

4、重建二叉树。已知前序遍历和中序遍历

 

前序遍历第一个节点是根结点,中序遍历根结点左边为左子树的结点,右边为右子树的结点;

通过递归构建左右子树

BinaryTreeNode * ConstructCore(int*startPreorder,int* endPreorder,int* startInorder,int* endInorder);

 

5、两个栈实现队列。(两个队列实现栈)

 

template<typename T> voidCQueue<T>::appendTail(const T& element){

stack.push(element);

}

 

Template<typename T> TCQueue<T>::deleteHead(){

//略

}

 

6、旋转数组的最小数字

 

常规算法时间复杂度O(n);

利用二分查找法并结合旋转数组的特性,将时间复杂度降为O(logn);

用两个指针分别指向数组第一个元素和最后一个元素,和中间元素进行比较。

 

7、斐波那契数列

 

动态规划实现,时间复杂度为O(n);

通过生僻数学公式实现,时间复杂度为O(logn)。

 

8、位运算。输入一个整数,输出该数二进制表示中1的个数。

 

方法:把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0。那么一个整数的二进制表示中有多少个1,就可以进行多少次操作。

 

与(&)、或(|)、异或(^)、左移(<<)、右移(>>)

左移n位时,最左边的n位将被丢弃,同时在最右边补上n个0;

右移n位时,最右边的n位将被丢弃;处理左边是,如果数字是一个无符号数值,则用0填补,如果数字是一个有符号数值,则用数字的符号位填补。

 

把整数右移一位和把整数除以2在数学上是等价的,但是除法的效率比移位运算要低得多。

 

正数边界:1、0x7FFFFFFF

负数边界:0x80000000、0xFFFFFFFF

 

9、3种错误处理方式

 

返回值:windows中很多API返回值为0代表API调用成功,而非零值定义了不同的意义,用来判断出错的原因。优点是和系统API一致;缺点是不能方便的使用计算结果。

全局变量:能方便使用计算结果;缺点是用户可能忘记检查全局变量。

异常:为不同出错原因定义不同的异常类型,逻辑清晰;缺点是很多语言比如C语言不支持异常,而且抛出异常时,程序的执行会打乱正常顺序,对性能有很大影响。

 

10、数值的整数次方

 

double Power(double base,int exponent)

考虑的问题:如果base等于0,exponent小于0,返回0,通过全局变量bool g_InvalidInput标识出错;

           判断base等于0,应该定义函数equal(base,0)不能直接base == 0,因为计算机在表示小数时都有误差,只能判断绝对值是不是在一个很小的范围内(base-0>-0.0000001)&&(base-0<0.0000001)。

 

11、打印1到最大的n位数

 

解法1:需要考虑大数问题,于是通过字符串模拟数字加法;

解法2:转换为数字排列的解法,然后将不是以0开始的字符串打印。

 

12、指针被delete后只是释放了所指内存,指针的指向没有变,所以需要将指针指向NULL

 

delete pNext;

pNext = NULL;

 

13、调整数组顺序使奇数位于偶数前面(或负数位于整数前面、或其他……)

 

考虑扩展性,利用函数指针:

void Reorder( int *pData,unsigned intlength,bool ( *func ) ( int ) ){

func(5);

}

bool isEven( int ) {

}

函数调用:Reorder( pData , length , isEven );

 

14、找出最小的K个数

 

解法1:基于Partition函数来解决,当index = =k-1时,数组左边的就是需要找的k个值。时间复杂度为O(n)。

解法2:O(nlogk)的算法,适合处理海量数据。创建一个大小为k的容器来存储k个数字,红黑树的查找、删除和插入操作都在O(logk)时间,在STL中set和multiset都是基于红黑树的。

 

15、连续子数组的最大和:动态规划

 

16、把数组排成最小的数{ 3,23,321}

 

通过自定义的比较函数来实现:两个数字m和n能拼成mn和nm,比较这两个数的大小来确定m和n的大小。

 

17、求第n个丑数(只包含因子2、3和5的数)

 

创建数组保存已经找到的丑数,用空间换时间的解法。

 

18、第一次只出现一次的字符:利用哈希表,时间复杂度O(n)

 

19、求数组中的逆序对

 

利用归并排序的方法,时间复杂度为O(nlogn),同时需要一个长度为n的辅助数组。

 

20、数字在排序数组中出现的次数

 

顺序扫描的时间复杂度为O(n)

通过二分查找第一个出现的需查找数字和最后一个出现的需查找数字的下标;

然后计算下标差,就能得到该数出现的次数。时间复杂度为O(logn)。

 

21、数组中只出现一次的数字

一个整型数组除了两个数组之外,其他的数组都出现了两次,找出这两个数字。要求时间复杂度为O(n),空间复杂度为O(1)

 

方法:首先从头到尾异或,异或结果的二进制表示中至少有一位为1;

     以第n位是不是1为标准把原数组分为两个数组;

     出这两个数字。

 

22、不用加减乘除做加法

 

while(num2 != 0){

sum = num1^num2;

carry = (num1 & num2);

num1 = sum;

num2 = carry;

}

return num1;

 

23、c++中,成员变量的初始化顺序只与它们在类中的声明顺序有关,而与在构造函数的初始化列表的顺序无关。

 

24、输入两个结点,求它们的最低公共祖先

 

分三种情况:1.该树是二叉搜索树时,树中从上到下第一个在两个输入结点的值之间的结点,就是最低公共祖先;

          2.树中有指向父结点的指针pParent,则转换成求两个链表的第一个公共结点;

          3.该树是普通的树,则通过辅助内存,得到到某一结点的路径,然后求出这两条路径的最后公共结点。

 

25、数组中重复的数字

 

方法1:排序     方法2:哈希表(需要辅助内存)    方法3:比较当前值和下标是否相等

 

26、字符串中第一个不重复的字符

 

利用哈希表(256),第一次遍历统计不同字符个数,第二次遍历找出第一个值为1的字符。

 

27、链表中环的入口结点

 

(1)指针P1和P2初始化时指向链表的头结点;(2)通过一快一慢指针,从相遇结点出发,统计环中结点个数nodes,然后P1先在链表中移动nodes步;(3)P1和P2再同时移动,直到相遇,相遇结点就是环的入口结点。

 

28、删除链表中重复的结点

 

考虑头结点可能与后面的结点重复,因此删除函数应该声明为:

void deleteDuplication(ListNode** pHead)而不是voiddeleteDuplication(ListNode* pHead)

 

29、二叉树中序遍历的下一个结点(有左右子结点的指针和有一个指向父结点的指针)

 

如果结点有右子树,则它的下一个结点是它右子树中的最左子结点;

如果没有右子树,而且结点是它父结点的左子结点,那么下一个结点就是父结点;

如果没有右子树,而且是它父结点的右子结点,则一直向上遍历,直到一个是它父结点的左子结点。如果这样的结点存在,那么这个结点的父结点就是要找的下一个结点。

 

30、二叉树打印成多行

 

利用queue进行层序遍历;设一个变量表示当前层还没打印的结点数,另一个变量表示下一层结点的数目。

 

31、按之字打印二叉树

 

利用两个栈来实现。

 

 

1 0