Swift 数据结构与算法(一) 链表 + Leetcode1474

133 阅读6分钟

链表

截屏2023-07-31 13.53.41.png

链表的基本操作

  1. 插入:将一个新元素插入链表的任意位置。 2.删除:将一个元素从链表中删除。
  2. 查找(遍历):查找一个特定的元素。
  3. 更新:更新一个特定节点上的元素。

使用 Swift 实现一个链表 代码较多, 代码实现在后面附录展示.

链表与数组的区别

截屏2023-07-31 17.40.46.png

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 练习

1474. 删除链表 M 个节点之后的 N 个节点

给定链表 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 ) 个节点。

  1. 我们创建一个虚拟头节点 dummy,并让 dummy 的 next 指向真正的头节点。现在,prev 和 cur 都指向 dummy。

    dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
    prev
    cur
    
  2. 我们保留前 ( m = 2 ) 个节点。这个过程中,prev 会移动到第 2 个节点,cur 会移动到第 3 个节点。

    dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
                  prev
                        cur
    
  3. 然后,我们删除接下来的 ( n = 3 ) 个节点。这个过程中,cur 会移动到第 6 个节点。

    dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
                  prev
                                            cur
    
  4. 通过修改 prev 的 next 指针,我们可以直接跳过那些被删除的节点。现在,prev 的 next 直接指向 cur。

    dummy -> 1 -> 2 -> 6 -> 7 -> 8 -> 9 -> 10
                  prev
                       cur
    
  5. 重复以上步骤,直到 cur 指向链表末尾的 null。最后,我们返回 dummy 的 next,也就是修改后的链表的头节点。

这就是使用双指针解决这个问题的思路。

考虑特殊边界条件

在处理这个问题时,需要注意以下几个条件:

  1. 当保留 ( m ) 个节点时,如果链表的节点数量不足 ( m ),我们应直接返回链表。在代码中,我们通过检查当前节点 cur 是否为 nil 来处理这种情况。

  2. 当删除 ( n ) 个节点时,如果链表的节点数量不足 ( n ),我们应删除所有剩余节点。在代码中,我们同样通过检查当前节点 cur 是否为 nil 来处理这种情况。

假设我们有一个链表:1->2,我们的任务是保留前 ( m = 3 ) 个节点,删除接下来的 ( n = 2 ) 个节点。

  1. 我们创建一个虚拟头节点 dummy,并让 dummy 的 next 指向真正的头节点。现在,prev 和 cur 都指向 dummy。

    dummy -> 1 -> 2
    prev
    cur
    
  2. 我们尝试保留前 ( m = 3 ) 个节点。但链表的节点数量不足 ( m ),所以我们直接返回链表。

    dummy -> 1 -> 2
    prev
             cur
    

假设我们有一个链表:1->2->3,我们的任务是保留前 ( m = 2 ) 个节点,删除接下来的 ( n = 3 ) 个节点。

  1. 我们创建一个虚拟头节点 dummy,并让 dummy 的 next 指向真正的头节点。现在,prev 和 cur 都指向 dummy。

    dummy -> 1 -> 2 -> 3
    prev
    cur
    
  2. 我们保留前 ( m = 2 ) 个节点。这个过程中,prev 会移动到第 2 个节点,cur 会移动到第 3 个节点。

    dummy -> 1 -> 2 -> 3
                  prev
                       cur
    
  3. 我们尝试删除接下来的 ( n = 3 ) 个节点。但链表的节点数量不足 ( n ),所以我们删除所有剩余节点。这个过程中,cur 会移动到链表末尾的 nil

    dummy -> 1 -> 2 -> 3
                  prev
                            cur
    
  4. 通过修改 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 是整个链表的头节点,22 -> 3 -> 4 -> 5 这个链表的头节点,33 -> 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