rambo

链表

单链表,头结点不存数据域。

想在链表中找到它的第i个结点,只能从头结点开始,沿着指向下一个节点的指针遍历链表,它的时间效率是O(n)。而在数组中,可以根据下标在O(1)时间内找到第i个元素。

xiangqian

QQ截图20150525140602

list1:向前生成算法;list2:向后生成算法。

QQ截图20150828004412

相关试题:

1、输入一个单向链表,输出该链表中倒数第k个结点

  • 自然可以想到,先走到链表的尾端,再从尾端回溯k步。可是输入的是单向链表,只有从前往后的指针而没有从后往前的指针。因此我们需要打开我们的思路。
  • 假设整个链表有n个结点,那么倒数第k个结点是从头结点开始的第n-k-1个结点(从0开始计数)。计算链表的长度n,只要从头结点开始往后走n-k-1步就可以了。

时间复杂度是O(n),但需要遍历链表两次。第一次得到链表中结点个数n,第二次得到从头结点开始的第n-k-1个结点即倒数第k个结点。

  • 上述存在缺点:如果输入的链表的结点个数 很多,有可能不能一次性把整个链表都从硬盘读入物理内存,那么遍历两遍意味着一个结点需要两次从硬盘读入到物理内存。

能不能把链表遍历的次数减少到1?

解决方法:在遍历时维持两个指针,第一个指针从链表的头指针开始遍历,在第k-1步之前,第二个指针保持不动;在第k+1步开始,第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在k+1,当第一个(前面的)指针到达链表的尾结点时,第二个指针(后面的)指针正好是倒数第k个结点(尾结点为倒数第0个结点)。

注意:想好边界条件再开始编写代码,再者要在编写完代码之后再用边界值、边界值减1、边界值加1都运行一次。

在写代码的时候需要考虑鲁棒性,最好采用防御性编程,就是考虑在哪些地方会出错,然后提前加上错误判断,这样避免因此错误输入而导致程序崩溃。

  • 输入的pListHead为空指针,由于代码会尝试访问空指针指向的内存,程序崩溃
  • 输入的以pListHead为头结点的链表的结点少于k,又会出现尝试访问空指针指向的内存。
  • 输入的参数k小于等于0。根据题意k的最小值应为1。

2、删除单链表倒数第K个节点(思路和第一题一样,找到倒数第k个结点)

删除一个结点的实现:

3、输入一个单向链表。如果该链表的结点数为奇数,输出中间的结点;如果链表结点数为偶数,输出中间两个结点前面的一个。

为了解决这个问题,可以定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。当走得快的指针走到链表的末尾时,走得慢的指针正好在链表的中间。

  • 在单向链表中,每个结点都包含一个指向下一个结点的指针,最后一个结点的这个指针被设置为空。但如果把最后一个结点的指针指向链表中存在的某个结点,就会形成一个环,在顺序遍历链表的时候,程序就会陷入死循环。我们的问题就是,如何检测一个链表中是否有环,如果检测到环,如何确定环的入口点(即求出环长,环前面的链长)。

解法1:从头开始遍历链表,把每次访问到的结点(或其地址)存入一个集合(hashset)或字典(dictionary),如果发现某个结点已经被访问过了,就表示这个链表存在环,并且这个结点就是环的入口点。这需要O(N)空间和O(N)时间,其中N是链表中结点的数目。

解法2:使用p、q两个指针,p总是向前走,但q每次都从头开始走,对于每个节点,看p走的步数是否和q一样。如图,当p从6走到3时,用了6步,此时若q从head出发,则只需两步就到3,因而步数不等,出现矛盾,存在环。

解法3:O(1)空间、O(N)时间

准备两个指针,同时从链表头出发,一个每次往前走一步,另一个每次往前走两步。如果链表没有环,那么经过一段时间,第二个(速度较快的)指针就会到达终点;但如果链表中有环,两个指针就会在环里转圈,并会在某个时刻相遇。

链表形状类似数字 6 。
假设甩尾(在环外)长度为 a(结点个数),环内长度为 b 。则总长度(也是总结点数)为 a+b 。
从头开始,0 base 编号。将第 i 步访问的结点用 S(i) 表示。i = 0, 1 ...

带环的链表

分析追赶过程:
两个指针分别前进,假定经过 x 步后,碰撞。则有:S(x)=S(2x)
由环的周期性有:2x=tb+x 。得到 x=tb 。
另外,碰撞时必须在环内,不可能在甩尾段,有 x>=a 。

连接点为从起点走 a 步,即 S(a)。
S(a) = S(tb+a) = S(x+a)。
得到结论:从碰撞点 x 前进 a 步即为连接点。

根据假设易知 S(a-1) 在甩尾段,S(a) 在环上,而 S(x+a) 必然在环上。所以可以发生碰撞。同为前进 a 步,同为连接点,所以必然发生碰撞。

综上,从 x 点和从起点同步前进,第一个碰撞点就是连接点。

假设单链表的总长度为L,头结点到环入口的距离为a,环入口到快慢指针相遇的结点距离为t,环的长度为r,慢指针总共走了s步,则快指针走了2s步。另外,快指针要追上慢指针的话快指针至少要在环里面转了一圈多(假设转了n圈加x的距离),得到以下关系:
   s = a + t;
   2s = a + nr + t;
   =>a + t= nr;
   =>a = nr - t;

分别从碰撞点、头指针开始走,相遇的那个点就是连接点

4、反转单链表
思路一、新建一个链表,可以采用向后生成链表算法或者向前生成链表算法。

QQ截图20150828010928

定义3个指针,分别指向当前遍历到的结点、它的前一个结点及后一个结点

2)用递归实现反转链表?

 

5、从尾到头打印链表

通常打印只是一个只读操作,不能改变链表的结构。

遍历的顺序是从头到尾,可输出的顺序却是从尾到头。“先进后出”,这是一种栈的思想。

递归在本质上就是一个栈结构!!! 先递归输出它后面的结点,然后再输出该结点自身。

有个问题:当链表非常长的时候,就会导致函数调用的层级很深,从而有可能导致函数调用栈溢出。

6、合并两个排序的链表(要求合并后仍旧是递增排序)

QQ截图20150828011849

 

QQ截图20150828012558

7、大数相乘