链表
链表的基本操作
- 插入:将一个新元素插入链表的任意位置。 2.删除:将一个元素从链表中删除。
- 查找(遍历):查找一个特定的元素。
- 更新:更新一个特定节点上的元素。
使用 Swift 实现一个链表 代码较多, 代码实现在后面附录展示.
链表与数组的区别
Linked List vs. Array
Array- 一组内存里连续的数据
• 能用index访问 —O(1) Access
。 添加元素直接添加在最后 — Amortized O(1) Add(考虑到扩容) 。 删除元素需要挪动后面所有元素位置 —O(n) Delete
Linked List -内存里不一定连续的数据
• 不能用index访问 —O(n) Access
• 添加元素添加在最后 —0(n) Add,双链表O(1) 。 删除元素需要找到元素位置 — O(n)Delete
链表 Leetcode 练习
给定链表 head 和两个整数 m 和 n. 遍历该链表并按照如下方式删除节点:
- 开始时以头节点作为当前节点.
- 保留以当前节点开始的前
m个节点. - 删除接下来的
n个节点. - 重复步骤 2 和 3, 直到到达链表结尾.
在删除了指定结点之后, 返回修改过后的链表的头节点.
示例 1:
输入: head = [1,2,3,4,5,6,7,8,9,10,11,12,13], m = 2, n = 3
输出: [1,2,6,7,11,12]
解析: 保留前(m = 2)个结点, 也就是以黑色节点表示的从链表头结点开始的结点(1 ->2).
删除接下来的(n = 3)个结点(3 -> 4 -> 5), 在图中以红色结点表示.
继续相同的操作, 直到链表的末尾.
返回删除结点之后的链表的头结点.
思路
假设我们有一个链表:1->2->3->4->5->6->7->8->9->10,我们的任务是保留前 ( m = 2 ) 个节点,删除接下来的 ( n = 3 ) 个节点。
-
我们创建一个虚拟头节点 dummy,并让 dummy 的 next 指向真正的头节点。现在,prev 和 cur 都指向 dummy。
dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 prev cur -
我们保留前 ( m = 2 ) 个节点。这个过程中,prev 会移动到第 2 个节点,cur 会移动到第 3 个节点。
dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 prev cur -
然后,我们删除接下来的 ( n = 3 ) 个节点。这个过程中,cur 会移动到第 6 个节点。
dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 prev cur -
通过修改 prev 的 next 指针,我们可以直接跳过那些被删除的节点。现在,prev 的 next 直接指向 cur。
dummy -> 1 -> 2 -> 6 -> 7 -> 8 -> 9 -> 10 prev cur -
重复以上步骤,直到 cur 指向链表末尾的 null。最后,我们返回 dummy 的 next,也就是修改后的链表的头节点。
这就是使用双指针解决这个问题的思路。
考虑特殊边界条件
在处理这个问题时,需要注意以下几个条件:
-
当保留 ( m ) 个节点时,如果链表的节点数量不足 ( m ),我们应直接返回链表。在代码中,我们通过检查当前节点
cur是否为nil来处理这种情况。 -
当删除 ( n ) 个节点时,如果链表的节点数量不足 ( n ),我们应删除所有剩余节点。在代码中,我们同样通过检查当前节点
cur是否为nil来处理这种情况。
假设我们有一个链表:1->2,我们的任务是保留前 ( m = 3 ) 个节点,删除接下来的 ( n = 2 ) 个节点。
-
我们创建一个虚拟头节点 dummy,并让 dummy 的 next 指向真正的头节点。现在,prev 和 cur 都指向 dummy。
dummy -> 1 -> 2 prev cur -
我们尝试保留前 ( m = 3 ) 个节点。但链表的节点数量不足 ( m ),所以我们直接返回链表。
dummy -> 1 -> 2 prev cur
假设我们有一个链表:1->2->3,我们的任务是保留前 ( m = 2 ) 个节点,删除接下来的 ( n = 3 ) 个节点。
-
我们创建一个虚拟头节点 dummy,并让 dummy 的 next 指向真正的头节点。现在,prev 和 cur 都指向 dummy。
dummy -> 1 -> 2 -> 3 prev cur -
我们保留前 ( m = 2 ) 个节点。这个过程中,prev 会移动到第 2 个节点,cur 会移动到第 3 个节点。
dummy -> 1 -> 2 -> 3 prev cur -
我们尝试删除接下来的 ( n = 3 ) 个节点。但链表的节点数量不足 ( n ),所以我们删除所有剩余节点。这个过程中,cur 会移动到链表末尾的
nil。dummy -> 1 -> 2 -> 3 prev cur -
通过修改 prev 的 next 指针,我们可以直接跳过那些被删除的节点。现在,prev 的 next 直接指向 cur。
dummy -> 1 -> 2 prev cur
解题
/**
* Definition for singly-linked list.
* public class ListNode {
* public var val: Int
* public var next: ListNode?
* public init() { self.val = 0; self.next = nil; }
* public init(_ val: Int) { self.val = val; self.next = nil; }
* public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
* }
*/
class Solution {
// 定义函数deleteNodes,输入三个参数,分别为链表的头结点,需要保留的连续节点数和需要删除的连续节点数
func deleteNodes(_ head: ListNode?, _ m: Int, _ n: Int) -> ListNode? {
// 创建一个新的ListNode节点作为dummy节点,并把它的next指向head节点
let dummy = ListNode(0)
dummy.next = head
// 定义一个prev节点,它将指向当前待保留的节点的前一个节点
var pre:ListNode? = dummy
// 定义一个cur节点,它将指向当前待处理的节点,初始指向head
var cur:ListNode? = head
//
// 开始循环操作,直到cur节点为空(即到链表尾部)
while cur != nil {
// 循环保留m个节点
for _ in 0..<m {
// 如果当前节点cur为空,说明链表已经结束,直接返回dummy的next节点(也就是处理后的链表头结点)
if cur == nil {
return dummy.next
}
// 更新pre和cur节点,向后移动
pre = cur
cur = cur?.next
}
// 循环删除n个节点
for _ in 0..<n {
// 如果当前节点cur为空,说明链表已经结束,退出此次循环
if cur == nil {
break
}
// 更新cur节点,向后移动,相当于删除了cur指向的节点
cur = cur?.next
}
// 把pre节点的next指向cur,也就是跳过了n个节点
pre?.next = cur
}
// 最后返回处理后的链表头结点
return dummy.next
}
}
在链表数据结构中,每一个节点都可以看作是一个新的链表的头节点。
例如,如果我们有一个链表 1 -> 2 -> 3 -> 4 -> 5,那么 1 是整个链表的头节点,2 是 2 -> 3 -> 4 -> 5 这个链表的头节点,3 是 3 -> 4 -> 5 这个链表的头节点,以此类推。
所以 dummy.next 就是我们需要的最终,链表.
时空复杂度
本题解法的时空复杂度 是 O(n)
附 1 使用 Swift 实现一个链表
// 链表节点
**class** Node<T> {
**var** value: T
**var** next: Node<T>?
**init**(value: T) { // 节点的构造函数
**self**.value = value
}
**init**(value: T, next: Node<T>) {
**self**.value = value
**self**.next = next
}
}
**class** linkNode<T:Equatable> {
**private** **var** head: Node<T>?
**private** **var** tail: Node<T>? // 链表尾节点,方便末尾添加元素
**init**(head: Node<T>? = **nil**) {
**self**.head = head
}
//MARK - 链表尾部插入 新的 node
**func** insert(_ value: T) {
**let** newNode = Node(value: value)
**if** **let** tailNode = tail {
tailNode.next = newNode
} **else** {
head = newNode
}
tail = newNode
}
//**MARK: 删除指定的节点**
**func** delete(_ value: T) {
**if** **var** Node = head {
**if** Node.value == value {
head = Node.next
**if** Node.next == **nil** {
tail = Node
}
} **else** {
// 如果 Node.next != nil 就代表,链表还没结束, 只是中间某个节点,所以可以继续找下一个.
// 循环继续搜索,
**while** Node.next != **nil** {
**if** Node.next!.value == value {
// 如果找到要删除的节点,将其前一个节点的 next 指向删除节点的下一个节点
Node.next = Node.next!.next
// 如果删除的是尾节点,更新尾节点
**if** Node.next == **nil** {
tail = Node
}
**break**
}
Node = Node.next!
}
}
}
}
//MARK -
**func** display() {
**var** Node = head
**while** Node != **nil** {
print("\(Node!.value)")
Node = Node!.next
**if** Node != **nil** {
print("--->")
}
}
}
//**MARK: 在链表中查找指定的值**
**func** search(_ value: T) -> Bool {
**var** node = head
**while** node != **nil** {
**if** node?.value == value {
**return** **true**
}
node = node!.next
}
**return** **false**
}
}
**let** list = linkNode<Int>() // 创建一个新的链表
list.insert(1) // 在链表末尾插入 1
list.insert(2) // 在链表末尾插入 2
list.insert(3) // 在链表末尾插入 3
//list.display() // 打印链表,输出: 1 -> 2 -> 3