算法挑战 Day4 链表

50 阅读5分钟

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 个结点

labuladong 题解思路

给你一个链表,删除链表的倒数第 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 。

实现思路

  1. 节点A 从A链表遍历到结尾
  2. 节点B 从B链表遍历到结尾
  3. 遍历到结尾之后 节点A 从 B 链表开始遍历
  4. 节点B 从A 链表开始遍历
  5. 遍历到结尾之后 节点B 从 A 链表开始遍历
  6. 如果 存在 节点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

labuladong 题解思路

给定一个链表的头节点  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
    }