链表
单链表:
链表每一个结点的结构如下:
class ListNode:
def __init__(self, x):
self.val = x # 链表的值
self.next = None # 链表指向的下一个结点
如何实现一个链表? 如下所示:
class ListNode:
def __init__(self, x):
self.val = x # 链表的值
self.next = None # 链表指向的下一个结点
def printChain(node):
while node:
print(node.val)
node=node.next
if __name__ == '__main__':
# 1 -> 2 -> 3 -> None
l1=ListNode(1) # 定义一个链表结点
l2=ListNode(2)
l3=ListNode(3)
l1.next=l2
l2.next=l3
l3.next=None
printChain(l1)
3. 从尾到头打印链表
题目描述:
输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。
实现代码如下:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回从尾部到头部的列表值序列,例如[1,2,3]
def printListFromTailToHead(self, listNode):
# write code here
# 遍历每一个listNode,
# 把每一个node值循环放到(插入)列表的最前面,以此来实现倒叙
res=[]
pTmp = listNode # 一般用p代表指针,Tmp代表临时数据
while pTmp:
res.insert(0,pTmp.val) # 每一次循环都往头部添加(插入),放在列表的最前面
pTmp = pTmp.next
return res
题外话:python的list其实就是用链表(多个数据用一个指针)来实现动态增加长度
14. 链表中倒数第k个结点
题目描述:
输入一个链表,输出该链表中倒数第k个结点。
实现代码如下:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def FindKthToTail(self, head, k):
# write code here
#定义两个指针
firstNode=head
secondNode=head
for i in range(k):
if firstNode==None: # 判断是否为空链表
return None
firstNode=firstNode.next # 第一个指针先走k步
while firstNode:
firstNode=firstNode.next
secondNode=secondNode.next # 两个指针同时走,直到第一个指针走完
return secondNode # 此时第二个指针所在位置就是倒数第k个结点
15. 反转链表(★★)
题目描述:
输入一个链表,反转链表后,输出新链表的表头。
实现代码如下:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
# write code here
# 将现有的头换成尾,尾部的next为空
# 将从第二个node开始,循环将next指向前一个
# 需要一直有一个指针指向还没有翻转的链表的头部
# 需要考虑链表的长度
if pHead == None:
return None
if pHead.next == None:
return pHead
leftpointer = pHead
midpointer = pHead.next
rightpointer = midpointer.next
leftpointer.next = None
while rightpointer:
midpointer.next = leftpointer
leftpointer = midpointer
midpointer = rightpointer
rightpointer = rightpointer.next
midpointer.next = leftpointer
return midpointer
16. 合并两个排序的链表(★★)
题目描述:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解题思路:
1)递归
- 终止条件:当两个链表都为空时,表示我们对链表已合并完成。
- 如何递归:我们判断 pHead1 和 pHead2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)
图解如下:
2)一般解法
利用几个指针,对两个链表的值进行比较,用当前指针指向比较后的较小值,不断循环,直至其中一个指针指向null。
实现代码如下:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回合并后列表
def Merge(self, pHead1, pHead2):
# write code here
'''
# 递归解法
if not pHead1:return pHead2 # 终止条件,知道两个链表都为空
if not pHead2:return pHead1
if pHead1.val<=pHead2.val: # 比较的是链表中的值
pHead1.next = self.Merge(pHead1.next,pHead2) # 在类中,调用函数自身也需要用self
return pHead1
else:
pHead2.next = self.Merge(pHead1,pHead2.next)
return pHead2
'''
# 普通解法
# 利用了四个指针(pHead,prepointer,pTmp1,pTmp2)解题
if pHead1==None:
return pHead2
if pHead2==None:
return pHead1
pHead = pHead1 if pHead1.val<pHead2.val else pHead2
prepointer = pHead
pTmp1=pHead1
pTmp2=pHead2
if pHead == pTmp1:
pTmp1 = pTmp1.next
else:
pTmp2 = pTmp2.next
while pTmp1 and pTmp2:
if pTmp1.val<=pTmp2.val:
prepointer.next = pTmp1
prepointer = pTmp1
pTmp1 = pTmp1.next
else:
prepointer.next = pTmp2
prepointer = pTmp2
pTmp2 = pTmp2.next
if pTmp1==None:
prepointer.next = pTmp2
else:
prepointer.next = pTmp1
return pHead
25. 复杂链表的复制(★)
题目描述:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
解题思路:
第一步:在链表的每一个结点(node)后面复制一个一样的node
第二步:实现新node的random指向
第三步:断开原来的node和新的node之间的链接
注意: 边界条件(特殊情况)一定要考虑进去!!!
实现代码如下:
# class RandomListNode:
# def __init__(self, x):
# self.label = x
# self.next = None
# self.random = None
class Solution:
# 返回 RandomListNode
def Clone(self, pHead):
# write code here
# 在链表的每一个结点(node)后面复制一个一样的node
pTmp = pHead
# 这里需要判断pTmp是否为空,这是边界条件(特殊情况),一定要记得!!!
if pTmp==None:
return None
while pTmp:
# 首先建立一个node(根据上面的链表结构建立)
node = RandomListNode(pTmp.label)
node.next = pTmp.next
pTmp.next = node
pTmp = node.next
# 实现新node的random指向
pTmp = pHead
while pTmp:
# 这里需要判断pTmp.random是否为空,为空的话则.next不存在,一定要记得!!!
if pTmp.random:
pTmp.next.random = pTmp.random.next
pTmp = pTmp.next.next
# 断开原来的node和新的node之间的链接
pTmp = pHead
pnewTmp = pHead.next
res = pHead.next
while pTmp:
pTmp.next = pTmp.next.next
# 这里需要判断边界条件。当pnewTmp.next为空时,pnewTmp.next.next不存在,一定要记得!!!
if pnewTmp.next:
pnewTmp.next = pnewTmp.next.next
pTmp = pTmp.next
pnewTmp = pnewTmp.next
return res
36. 两个链表的第一个公共结点
题目描述:
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
实现代码如下:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def FindFirstCommonNode(self, pHead1, pHead2):
# write code here
pTmp1 = pHead1
pTmp2 = pHead2
while pTmp1 and pTmp2:
# 这里需要考虑两个链表长度一样的情况!!
if pTmp1 == pTmp2:
return pTmp1
# 当链表长度不一样时的情况,进行下一步操作
pTmp1 = pTmp1.next
pTmp2 = pTmp2.next
# 封装
# 第一个参数是比较短的链表指针
# 第二个参数是比较长的链表指针
# 第三个参数是比较短的链表表头
# 第四个参数是比较长的链表表头
def findnode(shortpointer,longpointer,shortHead,longHead):
k = 0
# 寻找两链表长度之间的差值k
while longpointer:
longpointer=longpointer.next
k += 1
# 先让长的链表走k步
shortpointer = shortHead
longpointer = longHead
for i in range(k):
longpointer = longpointer.next
while shortpointer != longpointer:
shortpointer = shortpointer.next
longpointer = longpointer.next
return shortpointer
if pTmp1:
return findnode(pTmp2,pTmp1,pHead2,pHead1)
'''
k = 0
while pTmp1:
pTmp1=pTmp1.next
k += 1
pTmp1 = pHead1
pTmp2 = pHead2
for i in range(k):
pTmp1 = pTmp1.next
while pTmp1 != pTmp2:
pTmp1 = pTmp1.next
pTmp2 = pTmp2.next
return pTmp1
'''
if pTmp2:
return findnode(pTmp1,pTmp2,pHead1,pHead2)
'''
k = 0
while pTmp2:
pTmp2=pTmp2.next
k += 1
pTmp1 = pHead1
pTmp2 = pHead2
for i in range(k):
pTmp2 = pTmp2.next
while pTmp1 != pTmp2:
pTmp1 = pTmp1.next
pTmp2 = pTmp2.next
return pTmp1
'''
46. 孩子们的游戏(圆圈中最后剩下的数)
题目描述:
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
解题思路:
这其实就是一个约瑟夫环的简化版本,
约瑟夫环的公式推导可参见链接:约瑟夫环——公式法(递推公式)
实现代码如下:
class Solution:
def LastRemaining_Solution(self, n, m):
# write code here
# 通过推导公式可得:f(n)=(f(n-1)+m)%n
if n<1 and m<1:
return -1
if n==1:
return 0
value=0
for index in range(2,n+1):
currentValue = (value + m)%index
value = currentValue
return value
55. 链表中环的入口结点
题目描述:
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解题思路:
- 设置快慢指针fast和low,fast每次走两步,low每次走一步。假如有环,两者一定会相遇(因为low一旦进环,可看作fast在后面追赶low的过程,每次两者都接近一步,最后一定能追上)。
- 相遇时,设
链表头到环入口长度为–s
环入口到相遇点长度为–d
相遇点到环入口长度为–m
可得出式子:s=(n-1)(m+d)+d ,意思是: 链表头到环入口的距离=相遇点到环入口的距离+(n-1)圈环长度。其中n>=1,所以n-1>=0圈。 - 所以两个指针分别从链表头和相遇点继续出发,每次走一步,最后一定相遇于环入口。
实现代码如下:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def EntryNodeOfLoop(self, pHead):
# write code here
# 需要定义两个指针,其中一个跳两步(fast),一个跳一步(slow)
# 循环跳
# 要么是快的指针为空(没有环),要么是快慢指针相遇(有环)
fast = pHead
slow = pHead
if pHead == None:
return None
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
break
if fast == None or fast.next == None:
return None
# 如果slow走了L长度,那么fast就走了2L长度
# 假设从开始到入口点的长度是s,slow在环里里面走的长度是d
# 那么L=s+d
# 假设环内slow没走的长度是m,fast走的长度就是2L=s+n(m+d)+d
# 带入s+n(m+d)+d=2*(s+d),得出s=m+(n-1)(m+d)
# 从以上公式可以得出,两个指针分别从链表头和相遇点出发,最后一定相遇于环入口。
fast = pHead
while fast != slow:
fast = fast.next
slow = slow.next
return fast