1、前言
数组、链表、二叉树、动态规划、栈与队列是面试中常考的知识点,而在这几个知识点中,链表偏基础,但由于涉及指针的操作,但是却很能考察学生的编程功底,因此在这里总结一下二叉树常考的问题,包括:
(1)从尾到头输出链表;
(2)在O(1)时间内删除链表节点;
(3)链表中的倒数第K个节点;
(4)两个链表的第一个公共节点;
(5)反转链表;
(6)合并两个有序的链表;
(7)复杂链表的复制;
(8)判断环形链表中是否有环;
(9)找到环形链表环的入口;
(10)求环形链表环的长度;
2、链表中的常见问题
(1)从尾到头输出链表
基本思路:由于是单向链表,所以只能从头到尾进行遍历,要想从尾到头输出链表,一种可行的方法是在从头到尾遍历的时候,将每个遍历节点的值压入栈中。遍历完后,将栈中的元素一一弹出,即实现了从尾到头输出链表。
代码实现:
def TailtoHeadPrint(pHeadNode):
stack = []
pNode = pHeadNode.nex
while pNode != None:
stack.append(pNode.val)
pNode = pNode.nex
while len(stack) != 0:
print(stack[-1])
stack.pop()
(2)在O(1)时间内删除链表节点
基本思路:在单向链表中删除一个节点有两种方法:
(1)从头开始遍历链表,找到要删除节点的前一个节点,然后将前一个节点的next设置为要删除节点的next,这种方法的时间复杂度为O(n)
(2)通过要删除节点的next找到其下一个节点,然后将下一个节点的内容复制覆盖要删除的节点,再将下一个节点删除。
方法2有很多问题需要注意,1)如果要删除的节点是尾节点,由于它没有下一个节点,所以还需要从头开始遍历;2)如果链表中只有一个节点(既是头节点也是尾节点),直接删除该节点;
代码实现:
def DeleteNode(pHeadNode,node):
if node.nex != None:
node_next = node.nex
node.val = node_next.val
node.nex = node_next.nex
del node_next
else:
pNode = pHeadNode
while pNode.nex != node:
pNode = pNode.nex
pNode.nex = None
del node
(3)链表的倒数第K个节点
基本思想:可以使用两个指针p1、p2,从头节点开始,p1先走K个节点,然后p1、p2一起走,当p1到达尾节点时,p1指向的即为倒数第K个节点。
注意以下边界条件:
1)当链表长度小于K的时候,倒数第K个节点不存在,返回空;
2)当链表为空时,倒数第K个节点也不存在,返回空
代码实现:
def ReciprocalkNode(pHeadNode,K):
length = 0
pNode = pHeadNode.nex
while pNode != None:
length += 1
pNode = pNode.nex
if length < K or pHeadNode.nex == None:
return None
pNode1 = pHeadNode
pNode2 = pHeadNode
i = 0
while i < K - 1:
pNode1 = pNode1.nex
i += 1
while pNode1.nex != None:
pNode2 = pNode2.nex
pNode1 = pNode1.nex
return pNode2
(4)两个链表的第一个公共节点
基本思路:由于是单链表,当两个链表A、B存在公共节点时,两个链表公共节点之后的所有节点都相同,即形成一个开口向左的"Y"字形,因此,我们可以使用两个指针p1、p2,p1指向链表A,p2指向链表B,假设链表A的比链表B长N个节点,则p1现在链表A上先走N个节点,然后p1、p2分别在链表A、B上同时走,直到p1、p2指向相同的节点为止。
代码实现:
def CommonNode(pHeadNode1,pHeadNode2):
if pHeadNode1.nex == None or pHeadNode2.nex == None:
return None
pNode1 = pHeadNode1.nex
pNode2 = pHeadNode2.nex
length1 = 0
length2 = 0
while pNode1 != None:
length1 += 1
pNode1 = pNode1.nex
while pNode2 != None:
length2 += 1
pNode2 = pNode2.nex
K = abs(length1 - length2)
longLinkList = None
shortLinkList = None
if length1 > length2:
longLinkList = pHeadNode1
shortLinkList = pHeadNode2
else:
longLinkList = pHeadNode2
shortLinkList = pHeadNode1
i = 0
while i < K:
longLinkList = longLinkList.nex
i += 1
while longLinkList.val != shortLinkList.val:
longLinkList = longLinkList.nex
shortLinkList = shortLinkList.nex
return longLinkList
(5)反转链表
基本思路:反转链表,就是不断地调整相邻两个节点的next指针,让后一个节点的next指针指向它的前一个节点。所以我们需要三个指针p1、p2、p3,p1指向相邻两个节点中的前一个节点,p2指向后一个节点,p3用于保存p2的下一个节点,以便下一次的迭代。
需要注意的边界条件是:
1)当链表为空或只有一个节点时,不需要调整指针;
代码实现:
def Reverse_LinkList(pHeadNode):
if pHeadNode.nex == None or pHeadNode.nex.nex == None:
return pHeadNode
pNode1 = pHeadNode.nex
pNode2 = pNode1.nex
pNode1.nex = None
pNode3 = None
new_pHeadNode = None
while pNode2 != None:
pNode3 = pNode2.nex
new_pHeadNode = pNode2
pNode2.nex = pNode1
pNode1 = pNode2
pNode2 = pNode3
Reverse_pHeadNode = LinkNode()
Reverse_pHeadNode.nex = new_pHeadNode
return Reverse_pHeadNode
(6)合并两个有序的链表
基本思路:合并两个有序链表的思路与合并两个有序数组的思想是一样的。
1)我们设置三个指针p1、p2、p3,p1指向第一个有序链表,p2指向第二个有序链表,p3指向合并后的新链表;
2)如果p1指向的节点值小于p2指向的节点值,则将p1指向的节点链接到p3的末尾,然后再将p1,p3向后移动一个节点;
3)如果p1指向的节点值大于或等于p1指向的节点值,则将p2指向的节点值链接到p3的末尾,然后再将p2,p3同时向后移动一个节点;
4)如果剩余的是第一个链表,则将第一个链表剩下的所有节点链接到p3的末尾;否则将第二个链表剩下的所有节点链接到p3末尾。
代码实现:
def Merge_LinkList(pHeadNode1,pHeadNode2):
pNode1 = pHeadNode1.nex
pNode2 = pHeadNode2.nex
Merge_pHeadNode = LinkNode()
pNode3 = Merge_pHeadNode
while pNode1 != None and pNode2 != None:
if pNode1.val < pNode2.val:
pNode3.nex = pNode1
pNode1 = pNode1.nex
pNode3 = pNode3.nex
else:
pNode3.nex = pNode2
pNode2 = pNode2.nex
pNode3 = pNode3.nex
if pNode1 != None:
pNode3.nex = pNode1
if pNode2 != None:
pNode3.nex = pNode2
return Merge_pHeadNode
(7)复杂链