文章目录
一、代码注意点:
(1)if not cur等价于if cur==None,if cur等价于if cur != None (2)虚拟头结点的构造方式:dummy = ListNode(0)或dummy=Listnode(next=head)
二、链表常用技巧:
(1)dummy_head,函数返回结果是这个结点的下一个位置。如果操作涉及了链表的第一个位置,dummy_head可以维护整个操作更加通用。(2)考察链表的操作其实就是考察指针的操作:包括反转链表、环形链表的判断和找环形点。
三、链表的题型总结
- 找两个链表的交点、判断一个链表是否环形>>用龟兔比赛(快慢指针)的解法,fast一定会在交点or环点追上slow
- 交换节点顺序【反转链表】
四、典型例题
(一)自身环形\两链相交
实质:找重复,重合处,用双指针、哈希表
- 实质:判断两个链表的末尾是否存在相同的部分
- 思路:让两个指针分别从A和B点往C点走,两个指针分别走到C后,又各自从另外一个指针的起点,也就是A指针第二次走从B点开始走,B指针同理,这样,A指针走的路径长度 AO + OC + BO 必定等于B指针走的路径长度 BO + OC + AO,这也就意味着这两个指针第二轮走必定会在O点相遇,相遇后也即到达了退出循环的条件。如果没有交点则返回None。
- 代码学习:cur_a=cur_a.next if cur_a else headB # 如果a走完了,那么就切换到b走,cur_a指的是cur_a!=None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
cur_a,cur_b=headA,headB
while cur_a!=cur_b:
cur_a=cur_a.next if cur_a else headB # 如果a走完了,那么就切换到b走
cur_b=cur_b.next if cur_b else headA # b走完了就切换到a
return cur_a # 知道两个结点相遇,表示找到交点
141. 环形链表
判断是否存在环
【法一】哈希表法:用哈希表记录结点,如果结点重复出现则说明链表有环
- 代码注意点:(1)hash表中加入结点是hash.add(cur);(2)要先判断是否在,这样比较快
- 实质:判断是否存在环的本质在于是否存在重复元素,可以用哈希表来解决。或者快慢指针。
class Solution:
def hasCycle(self, head: ListNode) -> bool:
hash=set()
cur=head
while cur:
if cur in hash:return True
else:
hash.add(cur)
cur=cur.next
return False
【法二】龟兔相遇法(快慢指针):如果有环,则龟兔一定会相遇。兔子一次两步,乌龟一次一步。
# 假设初始点是dummy,因为需要符合while的条件
if not head or not head.next:return False
start=ListNode(next=head)
fast=start.next.next
slow=start.next
while slow!= fast:
if not fast or not fast.next:return False
slow = slow.next
fast = fast.next.next
return True
142. 环形链表 II
求开始入环的第一个节点
- 代码注意点:(1)hash.get(),返回指定键的值,如果键不在字典中返回默认值 None 或者设置的默认值。因为是字典,所以要这么写,注意一下。
【法一】哈希表法
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
Hash = {}
cur = head
while cur:
if Hash.get(cur,-1) > 0: return cur # 已经出现过
else:Hash[cur] = 1
cur = cur.next
return
【法二】指针法
- 思路:首先,fast和slow一定会遇到。其次,可以证明,当fast和slow相遇之后,从头结点出发的点和slow指针一定会在环口相遇。因此,需要在fast走到末尾前,验证if是否fast和slow会相遇,如果if相遇,就让头和slow一直走while,知道相遇
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
fast,slow=head,head
while fast and fast.next:
fast=fast.next.next
slow=slow.next
if fast==slow:
p=slow
q=head
while p!=q:
p=p.next
q=q.next
return p
return None
(二)结点顺序的交换
- 指针法:
cur:当前用来遍历的指针
pre:要赋值的指针
temp:保存原cur指向的位置 - 实质:循环的两两交换元素顺序
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur=head
pre=None # 第一个元素最后指向的就是空的,当然也可以设置为pre=Listnode(next=head)这样的形式
while cur!=None:
temp=cur.next # 下一个遍历的位置
cur.next=pre
pre=cur # 希望指向的位置
cur=temp # 遍历的位置
return pre
- 递归法
reverse函数return的结果是,子问题解决之后的剩余问题的解决方式。 - 代码注意点:if not cur可以写成if cur != None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
def reverse(pre,cur):
if not cur:return pre # 如果cur为None,则说明链表已经遍历完了,返回链表的头
else:
temp=cur.next
cur.next=pre
return reverse(cur,temp) # 进入新的递归
return reverse(None,head)
92. 反转链表 II
https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/dong-hua-tu-jie-fan-zhuan-lian-biao-de-z-n4px/
#if not head.next:return head # 只有一个元素的情况
dummy = ListNode(next=head)
pre = dummy
count=1
# cur先走到left
while pre.next and count<=left-1: # pre走left-1步,cur走到left
#1到left需走left-1步
#for _ in range(left-1):
pre = pre.next
count+=1
cur = pre.next
# 开始反转,一直到cur走到right
while count and count<=right-1:
temp = cur.next # 存储结点
cur.next = temp.next
temp.next = pre.next
pre.next = temp
count+=1
return dummy.next
- 只要涉及涉及头结点的变换都需要设置虚拟头结点
- 涉及结点交换,一定要注意先后顺序
- 时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( 1 ) O(1) O(1)
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
res = ListNode(next=head) # 涉及头结点的变换都需要设置虚拟头结点,res用来保存虚拟头结点的位置
pre = res
while pre.next!=None and pre.next.next!=None: # 必须有pre的下一个和下下个才能交换,否则说明已经交换结束了
# 一共涉及三个结点:pre,cur,post对应最左,中间的,最右边的节点
cur = pre.next
post = pre.next.next
# 交换结点顺序,前两个严格,第三个任意
cur.next = post.next
post.next = cur
pre.next = post
# 更新循环
pre = pre.next.next
return res.next
(三)删除指定节点
19. 删除链表的倒数第 N 个结点
双指针法:
- 本质:涉及倒数元素的操作,一般都用双指针,一个指针先走n步,随后两个指针一起走,则快指针走到最后的时候,慢指针指向要删除的位置。
- 思路:要删除倒数第n个元素,则fast指针先走n+1步,之后fast和slow一起走。fast用于遍历,slow用于指向删除的索引位置。最后用覆盖法实现删除的操作。
- 代码注意点:while pre.next!=None and pre.next.next!=None可以写成while pre.next and pre.next.next
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
res = ListNode(next=head) # 涉及头结点的变换都需要设置虚拟头结点,res用来保存虚拟头结点的位置
pre = res
while pre.next!=None and pre.next.next!=None: # 必须有pre的下一个和下下个才能交换,否则说明已经交换结束了
# 一共涉及三个结点:pre,cur,post对应最左,中间的,最右边的节点
cur = pre.next
post = pre.next.next
# 交换结点顺序,前两个严格,第三个任意
cur.next = post.next
post.next = cur
pre.next = post
# 更新循环
pre = pre.next.next
return res.next # 返回头结点的位置
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy_head=ListNode(next=head) # 只要是最终返回链表的头结点,都需要设置虚拟头结点
fast,slow=dummy_head,dummy_head
# fast先走n+1步
n=n+1
while n!=0:
fast=fast.next
n-=1
while fast!= None : # 终止条件是fast走到最后
fast=fast.next
slow=slow.next
# 删除slow指向的元素,即覆盖
slow.next=slow.next.next
return dummy_head.next # 提前存储好的虚拟头结点的next就是新链表的头结点