冲冲冲。
链表问题
链表和队列的区别在于指针,而指针问题又会涉及到遍历,多指针等特别的解法,同时也可以结合其他的数据结构来解链表问题,例如回文链表等。
1.约瑟夫环问题
题目:这道题目的变体很多,但是剥去词藻,其本质就是约瑟夫环问题。有m个人,围成一个环,编号为 0、1、2、3、、、m-1,从第一个人开始循环报数,假设数到n的那个人出列,然后从下一个人继续数数,数到n出列,以此循环,最后那个人为胜利者,求胜利者的编号。
分析:这道题目可以暴力求解,就是构造一个带环的链表,然后模拟整个过程,最后剩下一个的时候就是得到了结果。但是我们这里采用另一种思路,其时间复杂度是O(n),(好像有O(logn)的解法)。其思想基于递归,有点像动态规划的状态转移。参考圆圈中最后剩下的数字。关键找到前一个过程和后一个过程的映射关系。以及关系等式的化简。首先定一个关于最后剩下的数字的函数f(n,m),在这n个数字中,第一个被删除的数字是k=(m-1)%n,在剩下的序列中,K+1排在前面,剩下的数字也是有一个关于最后剩下的数字的函数f‘(n-1,m),f(n,m)== f‘(n-1,m),接下来就是把剩下的数字做一个映射,使得最后剩下的数字逆映射到原来的序列中(映射定义为P,逆映射定义为P’=(x+k+1)%n。,带入x、k之后,我们可以得到一个递归公式,看代码吧。
1 | import sys |
2.判断回文问题
题目:判断一个链表是否是回文结构
分析:回文问题可以借助额外的数据结构来求解,改进算法也是基于此。将链表按顺序输入到一个栈中,然后再一一弹出,并对照原来的链表上的值。
1 | def is_palindrome_1(head): |
3.逆序链表
题目:将一个链表逆序
分析:需要靠指针来进行逆序操作,一共有三个指针,pre、next以及head(这是指向当前位置的指针,因为不用head了,就直接使用head,一般是用另一个别名来替代)。算是模板,就是组件,其他算法可以靠这个逆序来进一步完成。
1 | if not head or head.next == None: |
4.链表环问题
题目:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
分析:也是一个模板算法。在下一题中就有用到。这个算法分成三个部分,分别是找环中的某个点,计算环的长度,以及最后的找到环的入口节点。1.找环中的某点的思路就是用两个指针,一个慢指针,一个快指针。(慢指针走一步,快指针走两步)。当两个指针相遇时,一定是在环中的某个节点。2.计算环的长度就是直接标记一下第一步的那个节点,然后开始遍历,当再次遇到标记的节点时,走的步数就是环的长度。3.第三步则考虑从链表头节点开始走,绕过环一次后到达环入口的步数为m+n,m指的是从链表开头走到环入口一共走过的步数,n则是环的长度。我们让一个指针先走n步,然后再同步让另一个指针和这个指针一起走m步,当两个指针相遇时就是指向环入口的节点。
1 | class Solution: |
5.链表相交的问题
题目:请实现一个函数没如果两个链表相交,请返回相交的第一个节点;如果不相交,返回None。
分析:在剑指里面也有这一道题,但是在分析中没有提到链表是否存在环的问题,这里需要做一个补充。首先需要明确的一点是,两个相交的链表要嘛都是有环的,要嘛就都没有环。所以问题就变成了三个:
1.如何判断一个链表是否有环,如果有,则返回第一个进入环的节点。这里就是参考问题4;
2.如果链表无环,那如何判断链表是否相交?这里参考剑指,简单来说就是用两个链表的长度差来同步两个链表的遍历进度,然后看两个指针是否会相遇。
3.如果链表有环,如何判断链表是否相交?这个是新的问题。
(1)如果loop1 == loop2,那么两个链表的拓扑结构如下所示:
在这种情况下,有点类似问题二,只不过,这里把loop当做是两个链表的终点。
(2)如果loop1 != loop2,那么有两种情况:
让链表从loop1出发,如果在回到loop1之前遇到loop2,那么说明两个链表相交,返回loop1和loop2其中一个就行;如果没有遇到,那就说明两个链表不相交。
1 | # 这里仅写第三种问题 |
6.将搜索二叉树转换成双向链表
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
分析:以根为中心的迭代算法,将根左边转换完成之后,连接到根(注意是左部分的最右边的节点连接到根),将根右边转换完成后也连接到根。若存在左边的部分,则返回指向左部分的第一个节点,否则返回根。
1 | class Solution: |