Day4 链表
24. 两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入: head = [1,2,3,4]
输出: [2,1,4,3]
示例 2:
输入: head = []
输出: []
示例 3:
输入: head = [1]
输出: [1]
解题思路:
迭代
思路与算法
也可以通过迭代的方式实现两两交换链表中的节点。
创建哑结点 dummyHead,令 dummyHead.next = head。令 temp 表示当前到达的节点,初始时 temp = dummyHead。每次需要交换 temp 后面的两个节点。
如果 temp 的后面没有节点或者只有一个节点,则没有更多的节点需要交换,因此结束交换。否则,获得 temp 后面的两个节点 node1 和 node2,通过更新节点的指针关系实现两两交换节点。
具体而言,交换之前的节点关系是 temp -> node1 -> node2,交换之后的节点关系要变成 temp -> node2 -> node1,因此需要进行如下操作。
temp.next = node2 node1.next = node2.next node2.next = node1 完成上述操作之后,节点关系即变成 temp -> node2 -> node1。再令 temp = node1,对链表中的其余节点进行两两交换,直到全部节点都被两两交换。
两两交换链表中的节点之后,新的链表的头节点是 dummyHead.next,返回新的链表的头节点即可。
递归
可以通过递归的方式实现两两交换链表中的节点。
递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。
如果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。
用 head 表示原始链表的头节点,新的链表的第二个节点,用 newHead 表示新的链表的头节点,原始链表的第二个节点,则原始链表中的其余节点的头节点是 newHead.next。令 head.next = swapPairs(newHead.next),表示将其余节点进行两两交换,交换后的新的头节点为 head 的下一个节点。然后令 newHead.next = head,即完成了所有节点的交换。最后返回新的链表的头节点 newHead。
实现代码:
// 迭代
func swapPairs(_ head: ListNode?) -> ListNode? {
// 创建虚拟头节点
var dummyNode = ListNode(-1)
dummyNode.next = head
// 分别 创建 前节点:preNode 当前节点:curNode 后节点 :nextNode
var preNode:ListNode? = dummyNode ,
curNode = head ,
nextNode = head?.next
// 如果当前节点 以及 后节点不为nil
// 交换节点
// 跟新节点
while curNode != nil && nextNode != nil {
var temp = nextNode?.next
nextNode?.next = curNode
curNode?.next = temp
preNode?.next = nextNode
preNode = curNode
curNode = temp
nextNode = temp?.next
}
return dummyNode.next
}
// 递归
func swapPairs(_ head: ListNode?) -> ListNode? {
// 递归终止条件
if head == nil || head?.next == nil {
return head
}
let temp = swapPairs(head?.next?.next)
// 交换链表中的节点
var res = head?.next
res?.next = head
head?.next = temp
return res
}
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 个节点 ,慢指针 再跟快指针一起往前遍历 ,当快指针 遍历到结尾的时候 ,此时慢指针执行的下一个节点就是要删除的节点
实现代码
func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? {
// 创建虚拟头结点
let dummy = ListNode(-1,head)
// 创建慢指针
var slow:ListNode? = dummy
// 创建快指针
var fast:ListNode? = dummy
// 快指针先遍历N个节点
for _ in 0..<n {
fast = fast?.next
}
// 快慢指针一起遍历 ,一直到 快指针遍历到最后一个节点
while fast?.next != nil {
fast = fast?.next
slow = slow?.next
}
// 删除节点
slow?.next = slow?.next?.next
return dummy.next
}
面试题 02.07. 链表相交
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交 :
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出: Intersected at '8'
解释: 相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入: intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出: Intersected at '2'
解释: 相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,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 。
实现思路
- 节点A 从A链表遍历到结尾
- 节点B 从B链表遍历到结尾
- 遍历到结尾之后 节点A 从 B 链表开始遍历
- 节点B 从A 链表开始遍历
- 遍历到结尾之后 节点B 从 A 链表开始遍历
- 如果 存在 节点A == 节点B 则 存在相交的环
实现代码
func getIntersectionNode(_ headA: ListNode?, _ headB: ListNode?) -> ListNode? {
// 创建 节点A 等于headA 节点B 等于 headB
var curA = headA, curB = headB
// 如果 节点A != 节点B while 循环继续
while curA !== curB {
// 节点A 从A链表遍历到结尾 遍历到结尾之后 节点A 从 B 链表开始遍历
if curA != nil {
curA = curA?.next
} else {
curA = headB
}
// 节点B 从B链表遍历到结尾 遍历到结尾之后 节点B 从 A 链表开始遍历
if curB != nil {
curB = curB?.next
} else {
curB = headA
}
}
return curA
}
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
走了 k
步,那么快指针 fast
一定走了 2k
步:
fast
一定比 slow
多走了 k
步,这多走的 k
步其实就是 fast
指针在环里转圈圈,所以 k
的值就是环长度的「整数倍」。
假设相遇点距环的起点的距离为 m
,那么结合上图的 slow
指针,环的起点距头结点 head
的距离为 k - m
,也就是说如果从 head
前进 k - m
步就能到达环起点。
巧的是,如果从相遇点继续前进 k - m
步,也恰好到达环起点。因为结合上图的 fast
指针,从相遇点开始走k步可以转回到相遇点,那走 k - m
步肯定就走到环起点了:
所以,只要我们把快慢指针中的任一个重新指向 head
,然后两个指针同速前进,k - m
步后一定会相遇,相遇之处就是环的起点了
实现源码
func detectCycle(_ head: ListNode?) -> ListNode? {
// 初始化 快慢指针
var slow = head , fast = head
while slow != nil {
slow = slow?.next
fast = fast?.next?.next
// 相遇的位置
if slow === fast {
// 创建两个新的节点 分别从 head 以及 相遇的位置 触发
var node1 = head
var node2 = slow
// 再次相遇的点 就是 环相交的点
while node1 !== node2 {
node1 = node1?.next
node2 = node2?.next
}
return node1
}
}
return nil
}