剑指offer第二章读书笔记
一个热爱编程的人会对某种语言有比较深入的了解。通常这样的人对于新的编程语言上手也比较快,而且理解的比较深入。 --陈黎明(Microsoft,SDEII)
数据结构是技术面试的重点,熟练掌握常见的数据结构:数组、字符串、链表、树、栈以及队列。
数组&链表
二者区别如下图
| 比较项 | 数组 | 链表 |
|---|---|---|
| 逻辑结构 | 1. 数组在内存中连续,使用前需要先申请; 2. 元素增加时需要注意越界问题; | 1. 动态内存分配,在内存中是不连续的; 2. 新增时malloc,删除时free; |
| 内存结构 | 从栈分配内存,使用方便,但自由度小; | 从堆分配内存,自由度大,需要注意内存泄漏; |
| 访问效率 | 由于数组的内存是连续,可以按照索引读取,以O(1) | 不支持索引读取,需要访问某个元素需要从头开始查找; |
| 越界问题 | 数组大小分配时即固定,需要注意越界访问问题; | 能malloc成功则能够新增,一般不存在越界; |
题目一:找出数组中重复的数字
在长度为n的数组里面,所有数字都在[0, n-1]范围内。数组存在重复,但是不知道哪几个数字重复,也不知道每个数字重复了多少遍,请找出数组中任意一个重复的数字。
方法一:先排序后查找,时间复杂度O(nlogn), 空间复杂度O(1);
方法二:使用Hash表,如不存在则插入Hash表中,若存在则找到该数;时间复杂度O(n), 空间复杂度O(n);
方法三:时间复杂度O(n), 空间复杂度O(1);
重排数组,从头到尾依次扫描,令数组A,下标i,若A[i] == i, 则继续扫描下一个;若A[i]!=i,则比较A[A[i]]是否等于A[i], 若相等,则找到了一个重复的数字,若不相等则将Swap(A[A[i]] ,A[i]),目的是将数组中的数字&索引保持一致;因为在扫描的过程中,对于每一个位置最多交换2次就能找到最终的位置,因此总的时间复杂度为O(n),空间复杂度为O(1);
Q:为什么最多存在两次交换?
A:考虑序列A = {1,2,1,0 },首先i=0, A[i] == 1 != 0, 因此Swap(A[A[i]], A[i]), A = {2, 1, 1, 0}, 交换之后A[i]!=i仍然成立, 因此继续Swap(A[A[i]], A[i]), 此时A={0, 1, 1, 2}; 在这个过程中序列中的
2一共经过两次交换,第一次被动与1交换,因此2在1的目标位置,第二次是2主动寻找自己目标位置,与0进行交换,因此最多交换两次就能找到自己的最终位置;
题目二:不修改数组找出重复的数字
长度为n+1的数组里面所有数字都在[1, n]的范围内,所以数组至少存在一个数字是重复的。找出任意一个重复的数字,但不能修改输入的数组。
方法一:Hash表,时间复杂度O(n), 空间复杂度O(n);
方法二:二分+数目统计,时间复杂度O(nlogn), 空间复杂度O(1),这种方法只能找出,但是不能找全;
算法思路: 由于数组内的数字都在[1,n]的范围内,因此使用枢轴值m进行划分,
1~m&m+1~n两部分,然后统计统计落在1~m内的数字数目是否超过了m,若超过,则重复数字一定在1~m里面!
题目三:在二维数组中查找
在一个二维数组中,每一行按照从左到右递增排序,每一列按照从上到下递增排序,完成一个函数输入一个二维数组和一个整数,判断数组中是否含有该整数。
方法一:暴搜;
方法二:从左下角/右上角作为起始点,进行分析,在这里以左下角为例。
考虑: 在下面二维数组中找target=10
1 2 8 9 2 4 9 12 4 8 10 13 6 9 11 15
A[3][0]=6 < 10=>往右边找;A[3][1]=9 < 10=> 往右边找;A[3][2]=11 > 10=> 往上边找;A[2][2]=10 == 10==> Get it!Q:为什么这样找?左上为什么不可以?
A: 考虑左上,因为在行列中均为最小值,若出现target 更大,则不知道先找右边还是先找下边的数;
Q:如何想到是左下/右上开始?
A:考虑到一对矛盾关系,左下为行最小列最大,因此与目标值比较后能够知道从哪个方向开始找,右上亦然。
待续。。。