Leetcode
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
- 如果链表是奇数个的话,那最后剩下的一个节点不需要处理。
- 链表为空则返回空
使用虚拟头节点,这样处理每个节点的逻辑都是一样的。
我照着卡哥的思路,把步骤逐一画了出来,写的时候就清晰多了。
初始时,cur
指向虚拟头结点,然后进行如下三步(橙色的三步),
待我们完成指针的转换之后,再将cur
移动到temp1
,方便进行下一次的链表节点两两互换。
有一点值得注意的是,我一开始在写进入while loop条件的时候,并没有加cur.next
,我直觉觉得只要cur.next.next
能跑通就可以了。但是这样在遇到head
为空的节点的时候就会出问题,因为cur.next is None
,所以cur.next.next
就会报错。
# 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 = ListNode(next = head)
cur = dummy
while cur.next and cur.next.next:
temp1 = cur.next # 防止节点修改
temp3 = cur.next.next.next
cur.next = cur.next.next
cur.next.next = temp1
temp1.next = temp3
cur = temp1
return dummy.next
O(n)
O(1)
Leetcode
给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。
双指针的经典应用,如果要删除倒数第n
个节点,让fast
移动n
步,然后让fast
和slow
同时移动,直到fast
指向链表末尾。删掉slow
所指向的节点就可以了。
# 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 = ListNode(next = head)
fast, slow = dummy.next, dummy
#让fast指针先跑n次,确定与slow间隔的距离
for i in range(n):
fast = fast.next
#slow, fast指针一起跑,fast跑到None的时候,slow刚好知道要移除的节点的前一个节点
while fast:
slow = slow.next
fast = fast.next
#删除指定节点
slow.next = slow.next.next
return dummy.next
O(n)
O(1)
Leetcode
给你两个单链表的头节点
headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null
。
图示两个链表在节点 c1
开始相交:
这道题可以像卡哥那样,先判断两条链表的长度,求差值,再末端对齐,开始curA
和curB
两个指针同时遍历到尾部。此时我们就可以比较curA
和curB
是否相同,如果不相同,同时向后移动curA
和curB
,如果遇到curA
== curB
,则找到交点。
否则循环退出返回空指针。
另外一个写法比较简洁,我在leetcode评论区看到一个完整的思路解析
Krahets-双指针,链表相交
具体的思路是初始化A, B = headA, headB
。A
指针遍历完headA
链表之后再遍历headB
链表,B
指针遍历完headB
链表之后再遍历headA
链表。两个指针遍历的总长度是相同的,因为
len(A链表) + len(B链表) = len(B链表) + len(A链表)
在遍历的过程中判断A
是否等于B
,相等就返回A
,即为相交点,否则当遍历完两条链表之后,A
和B
也会同时遍历到None
的位置,这时候返回A
,即为None
,不存在相交点。
# 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]:
A, B = headA, headB
while A != B:
A = A.next if A else headB
B = B.next if B else headA
return A
O(a+b)
,两种写法在最差情况下,都需遍历 a + b a+b a+b 个节点。O(1)
,节点指针 A
, B
使用常数大小的额外空间。Leetcode
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回
null
。
为了表示给定链表中的环,使用整数pos
来表示链表尾连接到链表中的位置(索引从0
开始)。 如果pos
是-1
,则在该链表中没有环。
说明:不允许修改给定的链表。
x = ( n − 1 ) ( y + z ) + z x = (n - 1) (y + z) + z x=(n−1)(y+z)+z,这个公式意味着从头结点(A)出发一个指针,从相遇节点(B)也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点(C)。
# 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]:
# method 1 fast and slow pointer, tortoise and hare algorithm
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
return None
O(n)
,快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index
指针走的次数也小于链表长度,总体为走的次数小于 2n
O(1)
用set记录遍历过的节点,如果有重复的就返回。
# 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]:
#method 2
visited = set()
while head:
if head in visited:
return head
visited.add(head)
head = head.next
return None
O(n)
O(n)
Leetcode
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
使用虚拟头节点
在虚拟头节点初始化cur
,方便我们遍历两个链表
每次对list1
和list2
的val
进行判断,哪个比较小,cur.next
就赋值哪个
在赋值完之后记得移动cur
指针,否则最后return的时候只会有一个节点
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
head = ListNode()
current = head
while list1 and list2:
if list1.val < list2.val:
current.next = list1
list1 = list1.next
else:
current.next = list2
list2 = list2.next
# 赋值完之后移动指针
current = current.next
#有一条较短的链表已经遍历完,直接把长链表剩下的部分接上就好了
current.next = list1 or list2
return head.next
一些关于return a or b
和return a and b
的细节:
O(a+b)
O(1)
Leetcode
我当时写的时候觉得很好的一道题,因为结合了翻转和合并,还用到了快慢针来确定链表中值。
重排的思路是将后半部分的链表翻转,然后将其与前半部分的链表合并。
快慢指针判断中点位置,因为快针一次走两步,慢针一次一步,当快针遍历到尾部的时候,慢针所在位置是中点。
如图所示,快慢指针初始都从head
出发,while loop运行到fast
不能再前进为止(fast
每次走两个节点)。这种写法可以保证不论链表的长度奇偶,代码的逻辑都是一致。
翻转的思路是用双指针prev
和cur
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reorderList(self, head: Optional[ListNode]) -> None:
"""
Do not return anything, modify head in-place instead.
"""
fast, slow = head, head
# slow 的位置是中点
while fast.next and fast.next.next:
fast = fast.next.next
slow = slow.next
prev, cur = None, slow.next
slow.next = None
#翻转后半部分,经过翻转后的链表头为prev
while cur:
temp = cur.next
cur.next = prev
prev = cur
cur = temp
#合并
main, sub = head, prev
while sub:
nextNode = main.next
main.next = sub
main = sub
sub = nextNode
O(n)
,所以总体的复杂度也是O(n)
O(1)
还有另外一个比较简单的思路就是用到数组和双指针。利用数组的首尾双指针来定位需要连接的链表节点。
class Solution:
def reorderList(self, head: ListNode) -> None:
if not head:
return
vec = list()
node = head
while node:
vec.append(node)
node = node.next
i, j = 0, len(vec) - 1
while i < j:
vec[i].next = vec[j]
i += 1
if i == j:
break
vec[j].next = vec[i]
j -= 1
vec[i].next = None
O(n)
O(n)
,因为用到了数组来存储链表节点