文档链接:代码随想录
题目链接:24.两两交换链表中的节点
视频讲解:视频讲解
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
提示:
[0, 100]
内0 <= Node.val <= 100
思路:通过递归和指针操作来逐个处理链表中的节点对,并将它们的位置交换。
pre
、cur
和next
。pre
指向当前需要交换的前一个节点,cur
指向当前需要交换的第一个节点,next
指向当前需要交换的第二个节点之后的节点。next
指针来交换cur
和pre
的位置。这样,cur
节点就移动到了它原本位置的后面。swapPairs
函数来处理被交换的第二个节点之后的链表部分。这个递归调用是为了处理可能存在的下一个节点对。# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 如果链表为空或只有一个节点,则直接返回原链表头节点
if head is None or head.next is None:
return head
# 初始化三个指针:pre指向当前需要交换的前一个节点,cur指向当前需要交换的第一个节点,next指向当前需要交换的第二个节点之后的节点
pre = head # pre 用于暂存当前对的前一个节点
cur = head.next # cur 用于暂存当前对的第一个节点
next = head.next.next # next 用于暂存当前对的第二个节点之后的节点
# 进行节点交换,将cur和pre交换位置
# 注意这里并没有真的交换节点内部的值,而是通过改变节点的next指针来交换节点的位置
cur.next = pre # 将cur的next指向pre,完成第一个节点的交换
pre.next = self.swapPairs(next) # 递归地交换后面的节点对,并将结果链接到pre后面
# 返回交换后的新头节点(即原本的第二个节点)
return cur
思路:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy_head = ListNode(next=head) # 创建一个虚拟节点,作为新的链表头部
current = dummy_head # 定义一个指针current,初始指向虚拟头节点
# 当当前节点的下一个节点和下下个节点都存在时,进行交换操作
while current.next and current.next.next:
# 定义临时变量,暂存当前节点的下一个节点和下下个节点的下一个节点
temp = current.next # 第一个节点
temp1 = current.next.next.next # 第二个节点之后的节点
# 交换两个节点
current.next = current.next.next # 使当前节点的下一个节点指向下下个节点
current.next.next = temp # 使下下个节点指向原先的下一个节点
temp.next = temp1 # 使原先的下一个节点指向原下下个节点的下一个节点
current = current.next.next # 当指针current移动到下一对节点的起始节点
# 返回交换后的新链表头节点(即虚拟头节点的下一个节点)
return dummy_head.next
文档链接:代码随想录
题目链接:19.删除链表的倒数第N个节点
视频讲解:视频讲解
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
进阶: 你能尝试使用一趟扫描实现吗?
思路:由于链表只能从头到尾遍历,不能直接访问倒数第n
个节点。需要采用快慢指针
的方法。
快慢指针: 开始时”快指针“和”慢指针“都指向虚拟头节点
,然后快指针先向前移动n
步。此时,快指针和慢指针之间相隔n
个节点。接下来,我们同时移动快指针和慢指针,直到快指针到达链表的末尾(即快指针的next
指针为null)。同时慢指针会指向倒数第n+1
个节点。
删除节点: 当快指针到达链表的末尾时,我们可以通过修改慢指针的next
指针来删除倒数第n
个节点。将慢指针的next
指针指向倒数第n
个节点的下一个节点。
**返回头节点:**返回虚拟头节点的next
指针,它现在指向修改后的链表的头节点。
注意事项:
n
小于等于 0,则直接返回原链表。n
步,如果在这个过程中快指针变成了 null
,说明链表长度小于 n
,此时也直接返回原链表。next
不为 null
时才移动)。next
不为 null
,以防止空指针异常。通过这种方法,我们可以在一次遍历中删除链表的倒数第 n
个节点,而不需要知道链表的具体长度。
# Definition for singly-linked list.
# 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_head = ListNode(next=head) # 创建一个虚拟节点,作为新的链表头部
# 初始快慢指针,都指向虚拟头节点
fast = slow = dummy_head
# 快指针先走n步
for i in range(n):
fast = fast.next
# 如果快指针已经到达链表末尾,说明链表长度小于n
if not fast:
return head
# 快慢指针同时前进,直到快指针到达链表末尾,移动 n-1 步
while fast.next:
fast = fast.next
slow = slow.next
# 删除倒数第n个节点
slow.next = slow.next.next # slow指向倒数第n+1个节点
# 返回虚拟头节点的下一个节点
return dummy_head.next
文档链接:代码随想录
题目链接:160.相交链表
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal
- 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA
- 第一个链表listB
- 第二个链表skipA
- 在 listA
中(从头节点开始)跳到交叉节点的节点数skipB
- 在 listB
中(从头节点开始)跳到交叉节点的节点数评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA
和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
示例 2:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
提示:
listA
中节点数目为 m
listB
中节点数目为 n
1 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
listA
和 listB
没有交点,intersectVal
为 0
listA
和 listB
有交点,intersectVal == listA[skipA] == listB[skipB]
进阶: 你能否设计一个时间复杂度 O(m + n)
、仅用 O(1)
内存的解决方案?
思路:如果两个链表相交,那么从相交点开始,它们的后续节点是相同的。如果两个链表不相交,那么它们的尾节点一定不同。想办法让两个链表长度相同,pA遍历(A+B)拼接的链表,pB遍历(B+A)拼接的链表。当两个链表相交时,pA和pB最终会同时到达相交的节点。
pA
和 pB
分别指向链表 headA
和 headB
的头节点。headA
,同时使用 pA
记录当前节点。当遍历完 headA
时,我们将 pA
重新定位到 headB
的头节点。pB
从 headB
开始遍历。当遍历完 headB
时,我们将 pB
重新定位到 headA
的头节点。pA
和 pB
相遇时,它们要么都指向相交的起始节点,要么都指向 null
(如果两个链表不相交)。# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
# pA指向链表headA的头节点,pB指向链表headB的头节点
pA,pB = headA,headB
# 当pA不等于pB时,继续循环
while pA != pB:
# 如果pA不为None,则将pA移动到下一个节点;如果pA到达尾部,将pA重新定位到headB的头节点
pA = pA.next if pA else headB
# 如果pB不为None,则将pB移动到下一个节点;如果pB到达尾部,将pB重新定位到headA的头节点
pB = pB.next if pB else headA
return pA # pA和pB相遇的点就是相交的起始节点,或者它们都是null(不相交)
文档链接:代码随想录
题目链接:142.环形链表II
视频讲解:视频讲解
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
[0, 104]
内-105 <= Node.val <= 105
pos
的值为 -1
或者链表中的一个有效索引进阶: 你是否可以使用 O(1)
空间解决此题?
思路:使用快慢指针法,定义两个指针,快指针每次移动两步,慢指针每次移动一步。如果链表中存在换,那么快慢指针最终会在环内的某个节点相遇。想办法让快慢指针在环开始的位置相遇。当相遇时,将其中一个指针指向头节点,然后让它们以相同的速度(每次一步)移动快慢指针,直到它们再次相遇。再次相遇时所在的节点位置就是环开始的位置。
初始化快慢指针:
slow
和 fast
都初始化为链表的头节点 head
。检测环:
fast
指针将先于slow
。判断链表是否有环:
同时移动快慢指针,快指针 fast
每次移动两步,慢指针 slow
每次移动一步。
如果链表中有环,快指针和慢指针最终会在环内的某个节点相遇(即 slow == fast
)。
如果指针到达链表的尾部(即遇到None
或null
)。
如果链表中有环,由于fast
指针的移动速度是slow
指针的两倍,它们最终会在环内的某个节点上相遇。快指针到达链表尾部(即 fast
或 fast.next
为 null
),说明链表无环,返回 null
。
确定环的起始节点:
找到环的起始节点:
当快慢指针相遇时,说明链表有环。此时,将慢指针 slow
重新指向链表的头节点 head
。
为了找到环的起始节点,我们将slow
指针重新设置为链表的头节点,而fast
指针保持不变。
然后,两个指针都以相同的速度(每次一步)移动,直到它们再次相遇。
保持快指针 fast
不动(仍然在相遇点),然后同时以相同的速度(每次一步)移动快慢指针,直到它们再次相遇。
它们再次相遇的节点就是环的起始节点。
快慢指针再次相遇的节点就是环的起始节点。
原理:
当快慢指针在环内相遇时,慢指针已经走了k
步(其中k
是环外因为从环的起始节点到相遇点的距离,与从头节点到相遇点的距离是相同的(这可以通过快指针走过的距离是慢指针的两倍链表的长度加上环内某段距离)。
快指针走的距离是慢指针的两倍,也就是2k
步。
2 k = x + y + y + z 2k = x + y + y + z 2k=x+y+y+z
z = x z=x z=x
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 定义快慢指针,起始时都指向头节点
fast=slow=head
# 使用while循环来移动指针,直到快指针到达链表的末尾或遇到环
while fast and fast.next:
fast = fast.next.next # 快指针每次移动两步
slow = slow.next # 慢指针每次移动一步
# 如果快慢指针相遇,说明链表有环
if slow == fast:
# 慢指针重新指向头节点,快指针保持在相遇点
slow = head
# 快慢指针再次同时移动,直到找到环的起始节点
while slow != fast:
slow = slow.next
fast = fast.next
# 返回环的起始节点
return slow
# 如果不存在环,返回None
return None