开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
链表也是常被问到的题型,比如说环形链表、相交链表、合并链表、链表的中间结点、反转链表、回文链表等。使用到的技术会有虚拟头节点、双指针、递归等。
虚拟头节点
虚拟头节点,也就是“dymmy”节点。在有些题中,如果不适用“dymmy”虚拟节点,代码会复杂很多,而有了“dummy”节点这个占位符,可以避免处理空指针的情况,降低代码的复杂性。 虚拟头节点举例: 21. 合并两个有序链表
题解:创建一个虚拟头节点dumy,并保存虚拟头节点为head,当n1和n2都不为空时,遍历n1和n2接入到dumy中。剩下的不为空的n1或n2直接接入到dumy后面,最终返回head.next
class Solution {
func mergeTwoLists(_ list1: ListNode?, _ list2: ListNode?) -> ListNode? {
var dumy: ListNode? = ListNode(-1)
var head = dumy
var n1 = list1
var n2 = list2
while n1 != nil && n2 != nil {
if n1!.val < n2!.val {
dumy?.next = n1
n1 = n1!.next
}else {
dumy?.next = n2
n2 = n2!.next
}
dumy = dumy?.next
}
if n1 != nil {
dumy?.next = n1
}
if n2 != nil {
dumy?.next = n2
}
return head!.next
}
}
其他虚拟头节点的题:剑指Offer 18. 删除链表的节点
双指针
链表是内存不连续的,遍历的话只能从头开始一直遍历,使用双指针技巧可以减少遍历的复杂度。 双指针举例: 141. 环形链表
题解:创建两个指针slow和fast,slow每次走一步,fast每次走两步。当slow不为空fast不为空并且slow=fast,返回true。否则返回false。
class Solution {
func hasCycle(_ head: ListNode?) -> Bool {
if head == nil || head!.next == nil {
return false
}
var slow = head
var fast = head?.next
while slow != nil && fast != nil {
if slow === fast {
return true
}
slow = slow?.next
fast = fast?.next?.next
}
return false
}
}
其他双指针经典题:23. 合并K个升序链表、142. 环形链表 II、876. 链表的中间结点、19. 删除链表的倒数第N个结点、160. 相交链表
递归
链表不光有迭代的特性,还有递归的特性,递归在很多题型当中都会用到,对于递归算法,最重要的就是明确递归函数的定义。 递归题举例: 206. 反转链表
题解:last是反转完成的链表,也就是最后一个节点。现在的head.next到last都反转完成,现在需要把head也反转一下,所以head.next.next指向head,head.next指向nil,这样就反转完成了。递归的basecase是当head为nil或者head.next为nil直接返回head。
class Solution {
func reverseList(_ head: ListNode?) -> ListNode? {
if head == nil || head?.next == nil {
return head
}
var last = reverseList(head?.next)
head?.next?.next = head
head?.next = nil
return last
}
}
其他递归经典题:92. 反转链表 II、25. K个一组翻转链表、148. 排序链表
总结
链表的题也要多刷,也是属于基础结构,比较好培养算法思想。 其他链表题还有:234. 回文链表