链表相关 python
—————剑指offer—————
easy
1)JZ6 从尾到头打印链表
题目描述
*注意输入的是结点,输出的是数组
①方法一:打入栈,逆序输出
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
python3
class Solution:
def printListFromTailToHead(self , listNode: ListNode) -> List[int]:
# write code here 牛客里默认py3的格式
stack = []
while listNode:
stack.append(listNode.val)
listNode = listNode.next
return stack[::-1]
listnode是头节点,List[list]返回一个列表
python2 的默认自定义函数的格式是
class Solution:
def blaba(self, listnode):
liatnode头结点名
②方法二:递归 (但很有可能超过最大递归层数而报错)
class Solution:
def printListFromTailToHead(self , listNode: ListNode) -> List[int]:
# write code here
return self.printListFromTailToHead(listNode.next) + [listNode.val] if listNode else []
注意
Ⅰ 方法自身间的回溯需要 + self
Ⅱ 同一个类,不同对象间的互相调用回溯需要 + self
Ⅲ 方法内的函数递归自身不需要self(如print方法里我再自定义一个函数进行递归便不需要self)
这里的第一参数self表示创建的是类实例
Ⅳ 注意这个if else的使用, blablabla = a 的执行是在if的条件下,否则就 = else里的那个东西
Ⅴ 注意[]与()的使用,第一遍自己打的时候,listnode.val外面写成了(),这样是错的
Ⅵ 注意 + 是拼接的意思
Ⅶ 注意使用递归可能会因为超过最大递归层数而报错,谨慎使用(此题在牛客使用递归没有通过)
2)JZ24 反转链表
题目描述
给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点
其实输入都是一样的,输入一个链表的头节点
①方法一:最简单的思路,取出值,再装入新的链表里
数组切片,但是需要额外的空间存储链表
时O(n) 空O(n)
class Solution:
def ReverseList(self , head: ListNode) -> ListNode:
# write code here
stack = [] #把链表逆序存入一个数组里(从题 6学来)
while head:
stack.append(head.val)
head = head.next
stack = stack[::-1] #逆序数组
#学会怎么去新建一个结点
node = ListNode(0) #定义一个结点,给它填入值0,这个结点当作伪头结点
p = node # p是指向这个结点的指针
for num in stack: #意思是num从前往后取一遍stack里的值
p.next = ListNode(num) #往后链接新的结点,填入stack的值
p = p.next
return node.next #输出真正开始表达含义的头结点,node.next才能取出真正的值
②方法二:在1的基础上简化一下,把列表想象成一个栈,把切片操作改成列表的pop操作
class Solution:
def ReverseList(self , head: ListNode) -> ListNode:
# write code here
res = []
while head:
res.append(head.val)
head = head.next
#注意,这里就不需要逆序操作了,等着下面的pop即可
node = ListNode(0)
p = node
while res:#当res列表不为空的时候
p.next = ListNode(res.pop()) #注意这里pop函数要加()的
p = p.next
return node.next
注意:
Ⅰ 把列表名设置成stack老报错,改成别的就正常了,嗯,注意不要设置成特殊指代单词
Ⅱ res.pop() 默认pop尾部的那个
③方法三:双指针
时O(n) 空O(1)
class Solution:
def ReverseList(self , head: ListNode) -> ListNode:
# write code here
cur = head #定义一个指针,和head所指向的位置一样,即头结点
pre = None #定义一个指针,指向空,即None,理解为Null
while cur: #直到cur==None就退出
# 从左往后看去设计,不是从右往左看,要不然全乱套了
tmp = cur.next #设置一个指针,记录cur的后继节点,要不然会丢失
cur.next = pre # cur所在结点的箭头指向pre
#注意第一次写成了 cur = pre,错误,这样是循环不起来的,cur只能代表指针,不代表结点情况
#注意指针指向结点然后进行操作是为了改变结点之间的联系,而不是指针单方面更改位置
pre = cur #pre指针更改位置,指向cur所指的结点
cur = tmp #cur指针更改位置,指向tmp所指的结点,往后移
return pre #输出新的头结点
3)JZ25 合并两个排序的链表
题目描述
要求了空间复杂度o(1),考虑指针
①方法一:
时间O(M+N)
空间O(1) 节点引用 dum , cur 使用常数大小的额外空间,
思路:
class Solution:
def Merge(self , pHead1: ListNode, pHead2: ListNode) -> ListNode:
# write code here
p = node = ListNode(0) #定义两个指针,p是之后要动的,node原地不动
while pHead1 and pHead2: #注意逻辑用语是 and,如果这俩至少有一个为空
if pHead1.val < pHead2.val:
p.next = pHead1
pHead1 = pHead1.next
else:
p.next = pHead2
pHead2 = pHead2.next
p = p.next #也可以分别写进if else里,就是有点冗余
#if pHead1:
# p.next = pHead1
#else: p.next = pHead2
p.next = pHead1 if pHead1 else pHead2 #简便写法,如果phead1存在,就接phead1,否则接phead2
return node.next
4)JZ52 两个链表的第一个公共结点
题目解释
①方法一:栈实现
先把两个链表存储在2个栈里面,找到链表的第一个公共结点即为倒着向前数最后一个一样的结点
时间复杂度o(n)
空间复杂度o(n)
class Solution:
def FindFirstCommonNode(self , pHead1 , pHead2 ):#phead1和2是两个链表的头指针
# write code here
a, b = [], []
p, q = pHead1, pHead2 #重新定义两个指针,这样原来的链表还在
while p:
a.append(p)
#a.append(p.val) 不用这个是因为存入的就是结点,而不是值(存入了2个对象)
p = p.next
while q:
b.append(q)
q = q.next
ans = None #定义一个空指针
i = len(a)-1 #别忘记-1了,开始是0
j = len(b)-1
while i>=0 and j>= 0 and a[i] == b[j]:
#因为两个链表后续部分是共用的,所以可以直接==比较判断
#while和if连用了
ans = a[i]
i = i-1
j = j-1
return ans #返回对应结点的头指针
①方法二:指针实现
时间:o(a+b) 最差情况下(即 |a - b| = 1, c = 0),此时需遍历 a + b个节点。
空间:o(1) 节点指针 A , B 使用常数大小的额外空间
思路
def FindFirstCommonNode(self, pHead1, pHead2):
# write code here
a, b = pHead1, pHead2
while a != b:
a = a.next if a else pHead2 #注意这里是phead,不是b
b = b.next if b else pHead1 #注意这里是phead,不是a
#理解为
'''
if a:
a = a.next
else:
a = pHead2
if b:
b = b.next
else:
b = pHead1
'''
return a
5)JZ23 链表中环的入口结点(mid)
题目解释
方法:双指针
时间:o(n)
空间:o(1)
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
fast, slow = head, head #设置快慢指针,fast一次走2步,slow一次走一步,如果有环一定会相遇
while True: #常用循环入口
if not(fast and fast.next): return None
#判断fast是否为空,防止链表是空
#判断fast.next是否为空,防止无环,而且如果fast.next为空的话,无法取到.next.next值
#这里不判断fast.next.next,是因为下一轮还会判断,不必冗余
fast = fast.next.next #跳2步
slow = slow.next #跳1步
if (fast == slow): #找第一次相遇的时候,就跳出循环
break
#第一次相遇的时候,不一定是在环入口
#第一次相遇的时候,f走了2s步,且f=s+nb(从s的角度得出,走了s+绕b了n圈赶上f)
#得出s=nb,f=2nb,s想要回到环入口得再走a步
#a怎么来?刚好头结点到环入口就是a步,所以设置一个指针指向head,s跟着head走
#之后第一次重合时,就是走了a步
fast = head #fast的重新利用,回到头指针,寻找和s的第二次相遇(fast改位置后的第一次相遇)
while fast != slow:
fast = fast.next
slow = slow.next
return fast
6)JZ22 链表中倒数最后k个结点
题目解释
方法:双指针
fast指针一直和slow保持k个距离,当fast为null时,slow也就是倒数第k个结点
时间o(n)
空间o(1)
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
fast, slow = head, head
while k>0:
fast = fast.next
k = k-1
'''
这段也可以用for循环实现
for _ in range(k): 或者是for num in range(k),num从0取到k-1
fast = fast.next
'''
while fast:
fast = fast.next
slow = slow.next
return slow
注意牛客上的要求稍微不同,考虑到链表长度可能小于k的情况:
class Solution:
def FindKthToTail(self , pHead: ListNode, k: int) -> ListNode:
# write code here
fast, slow = pHead, pHead
for _ in range(k):
if fast: fast = fast.next
else: return None
while fast:
fast = fast.next
slow = slow.next
return slow
7)JZ18 删除链表的结点
题目解释
方法:双指针
注意删除结点操作是什么
时间o(n)
空间o(1)
class Solution:
def deleteNode(self , head: ListNode, val: int) -> ListNode:
# write code here
fast, slow = head, head
if fast is None: return None #排除空链表
else:
fast = fast.next
if (head.val == val): return head.next #提前处理万一删除的是头结点
while fast:
if fast.val == val:
slow.next = fast.next
return head
else:
fast = fast.next
slow = slow.next
8)JZ83 删除排序链表中的重复元素
题目解释
方法①:双指针
思路:
cur当作主指针,遍历链表,fast当作对比指针,寻找是否有相同的结点,如果有就当场删除
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur, fast = head, head
if cur is None: return None
fast = cur.next
while cur and fast:
if cur.val == fast.val:
cur.next = fast.next
fast = fast.next
else:
cur = fast
fast = fast.next
return head
方法②:单指针
思路:
因为fast始终仅仅只比cur前一位,所以fast可以用cur.next代替
注意,还要注释掉重复的语句和注释掉关于fast的更改语句(因为这里没有fast的定义!其实这两个好像是一个意思。。。反正是一定要注释掉关于fast指针的更改语句)
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur = head
if cur is None: return None
#fast = cur.next
while cur and cur.next:
if cur.val == cur.next.val:
cur.next = cur.next.next
#cur.next = cur.next.next 重复的语句
else:
cur = cur.next
#cur.next = cur.next.next 原本是更改fast的指向,这里不存在fast,所以删掉
return head
牛客上的例题更改了一下,重复的结点要全部删掉
方法①:双指针
思路:
因为要删掉重复的所有结点,所以要用一个指针pre来存住前面的结点防止链表丢失
class Solution:
def deleteDuplication(self, pHead: ListNode) -> ListNode:
# write code here
if not (pHead): return None
node = ListNode(-1) # 伪头结点
node.next = pHead
pre = node
cur = node.next
while cur and cur.next:
if cur.val == cur.next.val:
k = cur.val
while cur and cur.val == k:
pre.next = cur.next
cur = cur.next
else:
pre = cur
cur = cur.next
return node.next
方法②:单指针
思路:
发现方法一中pre和cur始终差一个位置,所以cur可以用pre.next代替,注意先删除cur的全部定义语句!,然后把所有cur相关的改成pre.next(为了好看和习惯,也可以最后通过全部例题后把pre改成cur参数)
时间o(n)
空间o(1)
class Solution:
def deleteDuplication(self, pHead: ListNode) -> ListNode:
# write code here
if not (pHead): return None
node = ListNode(-1) # 伪头结点
node.next = pHead
pre = node
#cur = node.next
while pre.next and pre.next.next:
if pre.next.val == pre.next.next.val:
k = pre.next.val
while pre.next and pre.next.val == k:
pre.next = pre.next.next
#cur = cur.next 先注释掉
else:
pre = pre.next
#cur = cur.next 先注释掉
return node.next
mid
9)JZ35 复杂链表的复制
题目描述
思路:
首先注意!不能直接 return head!!
深拷贝和浅拷贝的区别,这里题目会去比较链表结点的地址,如果相同就会不通过,也就是说必须自己创建一个新的一模一样的的链表才可以。
普通的链表结点定义如下:
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None):
self.val = int(x)
self.next = next
给定链表的头节点 head ,复制普通链表很简单,只需遍历链表,每轮建立新节点 + 构建前驱节点 pre 和当前节点 node 的引用指向即可。
但是本题的结点定义如下:
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
本题链表的节点新增了 random 指针,指向链表中的 任意节点 或者 null 。这个 random 指针意味着在复制过程中,除了构建前驱节点和当前节点的引用指向 pre.next ,还要构建前驱节点和其随机节点的引用指向 pre.random 。
本题难点,如何去构建各结点的random引用指向(普通的遍历复制无法解决)
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
cur = head
dum = pre = Node(0)
while cur:
node = Node(cur.val) # 复制节点 cur
pre.next = node # 新链表的 前驱节点 -> 当前节点
# pre.random = '???' # 新链表的 「 前驱节点 -> 当前节点 」 无法确定
cur = cur.next # 遍历下一节点
pre = node # 保存当前新节点
return dum.next
方法①:哈希表
利用哈希表的查询特点,考虑构建 原链表节点 和 新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。
# -*- coding:utf-8 -*-
# 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
if not (pHead):
return
dic = {} #创建哈希表
# 复制各个结点,并建立“原结点->新结点”的 Map 映射,添加键值对
cur = pHead
while cur:
dic[cur] = RandomListNode(cur.label) #这里注意定义新的结点的函数名是啥!!和对应方法名称!!
cur = cur.next
cur = pHead
# 构建新结点的 next 和 random 指向
while cur:
dic[cur].next = dic.get(cur.next)
dic[cur].random = dic.get(cur.random)
cur = cur.next
# 返回新链表的头结点
return dic[pHead]
注意: python字典中的get方法
python字典的get方法会返回指定键的值,dict.get(‘键’),返回“键”对应的“值”,如果键不在字典中则返回默认值None。
方法②:拼接+拆分
思路:
重点是如何构建random指向,思路是 cur.next.random = cur.random.next
(这里cur指向原链表的一个结点)
思路图:
1
2
3
4
5
6
7
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head: return #排掉空结点
# 1.进行结点复制
cur = head
while cur:
tmp = Node(cur.val)
tmp.next = cur.next
cur.next = tmp
cur = cur.next.next #第一遍打的时候忘记写了。。
# 2.进行random的记录,虽然原来的random不能直接用,但是random.next就是和random一模一样值的结点,直接用(存在的话)
cur = head
while cur:
if cur.random:
cur.next.random = cur.random.next
''' 省略的原因是本来默认就是none
else:
cur.next.random = None
'''
cur = cur.next.next
# 3.全部复制好后进行拆分链表
cur = head
res = ans = head.next #res是留下来记录复制好的整个链表的,ans进行遍历
while ans.next: #注意判断条件到底是谁,ans在前面,所以条件就是ans是倒数最后一个结点
cur.next = cur.next.next
ans.next = ans.next.next
cur = cur.next
ans = ans.next
cur.next = None # 新的链表用的是原来的none,所以还给原本的链表补一个none
return res
写的时候有点疑惑判断问题,其实就是被两条链表公用一个none影响了,在循环结束之后补上就好了。
注意:
循环的进行语句别忘记写,cur = cur.next…