Swift 数据结构与算法( 19 )链表 + S_Leetcode剑指 Offer 22. 链表中倒数第k个节点

60 阅读5分钟

概念

题目

剑指 Offer 22. 链表中倒数第k个节点

截屏2023-08-05 22.53.10.png 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

 

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

解题思路🙋🏻‍ ♀️

  1. 初始化两个指针:将两个指针都指向链表的头节点。我们称之为fastslow
  2. 移动快指针:先将fast指针向前移动k个节点。此时,fastslow之间的距离为k
  3. 同时移动快慢指针:然后,同时移动fastslow,每次都向前移动一个节点。当fast指针到达链表的尾部时,slow指针所指向的位置就是倒数第k个节点。
  4. 返回结果slow及其后面的节点就是答案。

为什么这个方法有效

fast指针走到链表尾部时,它总共走了N步(N是链表的总长度)。而此时,slow指针走了N - k步。所以,slow指针所在的位置就是倒数第k个节点。

边界思考🤔

注意

  • 在实际代码实现时,需要考虑链表长度小于k的情况。
  • 也要注意当链表为空或者k为0的边界情况。

这种方法只需要遍历链表一次,所以它的时间复杂度是O(N),其中N是链表的长度。

1. 原始代码中的错误:

  • 错误的边界条件:你的代码中,当k小于或等于0时,直接返回head。但实际上,当k小于或等于0时,应该返回nil,因为它不是一个有效的索引。
  • 快指针移动问题:在尝试将快指针向前移动k次之前,你没有检查链表的长度是否大于或等于k。这可能导致在后续代码中访问空指针,从而导致运行时错误。

2. 类似题型的注意事项:

  • 边界条件的重要性:在处理链表问题时,始终首先考虑边界条件,如链表为空、链表长度小于给定值等。
  • 双指针策略的正确使用:当使用两个指针遍历链表时(例如快慢指针),确保在移动任何一个指针之前都进行了空检查。
  • 考虑特殊输入:例如链表长度为1、需要返回的节点恰好是头节点或尾节点等情况。
  • 初始化指针:确保在开始时正确初始化所有指针,并在整个过程中保持正确的更新。

3. 其他建议:

  • 练习更多:链表是一种常见的数据结构,涉及许多常见的问题和技巧。多做练习可以帮助更熟悉这些模式,并在将来更容易识别和解决问题。
  • 审题是关键:确保完全理解了题目的要求。读题时,注意题目中的所有细节和约束条件,以确保解决方案满足所有要求。

演示

流程图

使用链表:[1,2,3,4,5] 和 ( k = 2 ) 为例,演示执行流程。

初始链表:[1 -> 2 -> 3 -> 4 -> 5]

流程图:

开始
 |
 V
设置初始指针:newHead = head, fast = head, slow = head, kn = 2
 |
 V
检查 kn 是否大于 0? (是)
 |
 V
fast 向前移动一步,变为指向 2,并使 kn  1
 |
 V
再次检查 kn 是否大于 0? (是)
 |
 V
fast 向前移动一步,变为指向 3,并使 kn  1
 |
 V
再次检查 kn 是否大于 0? (否,因为 kn = 0)
 |
 V
进入 while 循环,检查 fast 是否不为 nil (是, 因为 fast 指向 3)
 |
 V
fast 向前移动一步,变为指向 4
slow 向前移动一步,变为指向 2
 |
 V
再次进入 while 循环,检查 fast 是否不为 nil (是, 因为 fast 指向 4)
 |
 V
fast 向前移动一步,变为指向 5
slow 向前移动一步,变为指向 3
 |
 V
再次进入 while 循环,检查 fast 是否不为 nil (是, 因为 fast 指向 5)
 |
 V
fast 向前移动一步,变为指向 nil (因为 5 是链表的最后一个元素)
slow 向前移动一步,变为指向 4
 |
 V
再次进入 while 循环,检查 fast 是否不为 nil (否, 因为 fast  nil)
 |
 V
结束,返回 slow 指针,它现在指向值为 4 的节点

最终结果:[4 -> 5]

代码

class Solution {
    func getKthFromEnd(_ head: ListNode?, _ k: Int) -> ListNode? {
        // 如果k小于等于0,直接返回头节点
        if k <= 0 {
            return head
        }
        
        // 初始化两个指针:快指针和慢指针,都指向头节点
        var fast = head
        var slow = head
        
        // kn用于计数,表示还需要将快指针向前移动多少次
        var kn = k
        
        // 将快指针向前移动k次
        while kn > 0 {
            fast = fast?.next
            kn -= 1
            // 如果在移动快指针过程中,快指针变为nil,则表示链表长度小于k,返回nil
            if fast == nil {
                return nil
            }
        }
        
        // 当快指针不为nil时,同时移动快指针和慢指针。
        // 当快指针到达链表尾部时,慢指针正好指向倒数第k个节点
        while fast != nil {
            fast = fast?.next
            slow = slow?.next
        }
        
        // 返回慢指针,即倒数第k个节点
        return slow
    }
}


时空复杂度分析

O(N)

引用

本系列文章部分概念内容引用 www.hello-algo.com/

解题思路参考了 abuladong 的算法小抄, 代码随想录... 等等

Youtube 博主: huahua 酱, 山景城一姐,