剑指Offer读书笔记

109 阅读4分钟

剑指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交换,因此21的目标位置,第二次是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

1289
24912
481013
691115
  1. A[3][0]=6 < 10=>往右边找;
  2. A[3][1]=9 < 10=> 往右边找;
  3. A[3][2]=11 > 10=> 往上边找;
  4. A[2][2]=10 == 10==> Get it!

Q:为什么这样找?左上为什么不可以?

A: 考虑左上,因为在行列中均为最小值,若出现target 更大,则不知道先找右边还是先找下边的数;

Q:如何想到是左下/右上开始?

A:考虑到一对矛盾关系,左下为行最小列最大,因此与目标值比较后能够知道从哪个方向开始找,右上亦然。

待续。。。