目前整理几道可以使用双指针法解决的链表题目:19. 删除N,21. 合并2,23. 合并K,86. 分隔链表,141. 环形链表,142. 环形链表II,(160. 相交链表,876. 链表的中间结点暂时未整理),具体题目如下:
19. 删除链表的倒数第N个节点
题目重点:给定一个单链表和整数n,删除倒数第n个结点,返回新的链表头。
思路:想要去删除倒数第n个结点,我们需要获得它的前一个即倒数第n+1个的引用,此时使用双指针-快慢指针法可以完美解决,步骤如下:
-
创建一个虚拟头节点
dummy,它的.next指向原链表头,防止删除的是头结点不好处理; -
fast先向前走n步; -
然后
fast和slow一起走,直到fast到达链表尾部; -
此时
slow的位置就是待删除节点的前一个; -
删除:
slow.next = slow.next.next。
核心代码:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# 一步创建虚拟头节点
dummy = ListNode(0, head)
fast = slow = dummy
# Step 1: fast 先走 n 步
for _ in range(n):
fast = fast.next
# Step 2: fast 和 slow 同时移动
while fast.next:
fast = fast.next
slow = slow.next
# Step 3: 删除 slow 后面的节点
slow.next = slow.next.next
return dummy.next
21. 合并两个有序链表
题目重点:给定两个升序链表:list1,list2,需要将这两个链表拼接为一个新的升序链表并返回。
思路:两个指针分别遍历两个链表,比较大小,归并它们,具体步骤如下:
-
创建一个虚拟头节点
dummy,用一个指针cur追踪结果链表尾部; -
比较
list1和list2当前节点值,小的那个挂到cur.next; -
哪个链表节点用了,就把那个链表向前移动一步;
-
当某个链表为空了,直接把另一个链表剩余部分接上。
核心代码:
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(-1)
cur = dummy
while list1 and list2:
if list1.val <= list2.val:
cur.next = list1
list1 = list1.next
else:
cur.next = list2
list2 = list2.next
cur = cur.next
# 处理剩余链表(最多只剩一个非空)
cur.next = list1 if list1 else list2
return dummy.next
23. 合并K个有序链表
题目重点:输入:一个包含 k 个链表头节点的列表 lists(已按升序排列),目标:将这个 lists 合并成一个新的升序链表,并返回头节点。
思路:使用最小堆(优先队列)优化,即堆中每个元素是当前各链表的头节点,每次弹出堆顶的最小值,加入结果链表,并把该节点的 next 放入堆中。
核心代码:
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
# 初始化一个最小堆,可以使用它快速找到当前所有链表头中值最小的那个节点
min_heap = []
for i, node in enumerate(lists):
if node:
# 把元素放入堆中
heappush(min_heap, (node.val, i, node))
dummy = ListNode(-1)
cur = dummy
# 只要堆不空,就循环处理,说明还有节点没处理完。
while min_heap:
# 弹出堆中当前值最小的节点
val, i, node = heappop(min_heap)
cur.next = node
cur = cur.next
if node.next:
heappush(min_heap, (node.next.val, i, node.next))
return dummy.next
86. 分隔链表
题目重点:给定一个链表head和一个int值x,将 < x 的节点放到前面,>= x 的节点放到后面,需要保持每个节点的初始相对位置。
思路:上面的21题需要你将两个链表合二为一,这道题是将两个链表先一分为二,其中一个链表中的元素大小都小于 x, 另一个链表中的元素大小都大于等于 x,最后将这两条链表接到一起。注意最后拼接的时候需要将后半段链表(after链)尾节点的next断开!!!
核心代码:
class Solution:
def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
before_dummy = ListNode(-1)
after_dummy = ListNode(-1)
before = before_dummy # 尾指针
after = after_dummy # 尾指针
while head:
if head.val < x:
before.next = head
before = before.next
else:
after.next = head
after = after.next
head = head.next
# 结束后拼接两个链表
after.next = None # 把后半段(after链)尾节点的 next 断开,防止出现链表“环”或者“多余尾巴”的问题。
before.next = after_dummy.next
return before_dummy.next
141. 环形链表
题目重点:判断链表中是否存在环,即某个节点再次被访问。
思路:经典双指针-快慢指针法,使用两个指针 slow 和 fast,slow 每次走一步,fast每次走两步。
如果链表中存在环,两个指针一定会在某个时刻相遇;
如果无环,fast 会先到达链表尾部,退出循环。
核心代码:
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
142. 环形链表II
题目重点:不仅要判断有无环,还要找出入环的第一个节点,
思路:第一步:判断链表是否有环,和上道题思路一致,使用快慢指针去判断;第二步(核心):找到环的起始节点,让一个指针从头开始走,一个从相遇点开始走,再次相遇点即环起点。
为什么从相遇点和头节点同时出发会在环入口相遇?
假设:
a:从头节点到环的入口节点的距离(非环部分的长度)
b:从环的入口节点到第一次相遇点的距离
r:整个环的长度
那么:第一次相遇时慢指针走了 a + b,快节点走了 2(a + b),二者的差 a + b 则是环长 r 的整数倍,即 a + b = n x r。此时让一个指针 ptr 从头开始走,一个指针 slow 从相遇点开始走,则会在入环点相遇,此时返回 ptr。
核心代码:
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow = head
fast = head
# Step 1: 判断是否有环
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 如果找到了相遇点(slow == fast),说明有环 → 用 break 跳出 → 不执行 else
if slow == fast:
break
# 只有在 while 循环“正常结束”时(即未被 break 打断)才执行 else 中的语句。
else:
return None # 无环
# Step 2: 找入环点
ptr = head
while ptr != slow:
ptr = ptr.next
slow = slow.next
return ptr
&spm=1001.2101.3001.5002&articleId=147878576&d=1&t=3&u=222e22bc03544d10b78c193d229673ad)

被折叠的 条评论
为什么被折叠?



