《剑指 Offer》学习记录:题 22:链表中倒数第 k 个结点

题 22:链表中倒数第 k 个结点#

题干#

输入一个链表,输出该链表中倒数第 k 个结点。为了符合大多数人的习惯,本题从 1 开始计数,即链表的尾结点是倒数第 1 个结点。例如一个链表有 6 个结点,从头结点开始它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个结点是值为 4 的结点。——《剑指 Offer》P134

测试样例#

链表的数据结构定义如下(Python):

Copy Highlighter-hljs
class ListNode: def __init__(self, x): self.val = x self.next = None

若传入的头结点 head 指向的链表结构如图所示:

要求返回倒数第 4 个结点,也就是结点 2:

蛮力法#

方法思路#

蛮力法的思路比较简单,设表长为 n,为了得到第 n - k 个结点,就需要先获得表长。首先先遍历一遍链表求出表长,然后再遍历一次链表得到第 n - k 个结点。

题解代码#

Copy Highlighter-hljs
class Solution: def getKthFromEnd(self, head: ListNode, k: int) -> ListNode: count = 0 ptr = head while ptr: #遍历链表获得表长 ptr = ptr.next count = count + 1 ptr = head #遍历到 n - k 个结点 for i in range(count - k): ptr = ptr.next return ptr

时空复杂度#

设表长为 n,第一次遍历需要遍历整个链表,第二次遍历 n - k 次。得出 T(n) = 2n - k,进而得到 O(n) = n。
由于不需要任何辅助空间,蛮力法的空间复杂度为 O(1)。

快慢指针#

方法思路#

快慢指针的想法比较巧妙,也就是可以定义 2 个指针。先使用第一个指针遍历链表到表尾,此时若第二个指针和第一个指针差距 k 个结点,就可以直接得到第 n - k 个结点。例如对于链表 [1,2,3,4,5] 求倒数第 2 个结点,可以先让快指针先出发遍历 2 个结点,慢指针先不动。

接下来同时移动 2 个指针,当快指针遍历完毕时,由于慢指针晚出发 2 个结点,所以慢指针此时指向的就是倒数第 2 个结点。

题解代码#

Copy Highlighter-hljs
class Solution: def getKthFromEnd(self, head: ListNode, k: int) -> ListNode: fast_ptr = head slow_ptr = head for i in range(k): #快指针先出发 k 个结点 ptr = ptr.next while ptr: #快慢指针同时向前遍历 ptr = ptr.next slow_ptr = slow_ptr.next return slow_ptr

时空复杂度#

设表长为 n,遍历整个链表得到时空复杂度 O(n) = n。注意此时虽然时间复杂度和蛮力法一样,T(n) = n 有可能比蛮力法低。
由于不需要任何辅助空间,空间复杂度为 O(1)。

栈辅助法#

方法思路#

获得倒数第 k 个结点,可以认为是逆序链表后取到第 k 个元素。此时可以借助一个栈结构来让链表逆序,例如对于链表 [1,2,3,4,5] 求倒数第 2 个结点,可以先遍历一遍链表,将所有的数据元素入栈。此时就得到了链表的逆序序列,出栈栈顶的 k 个元素就能得到需要的结点。

题解代码#

Copy Highlighter-hljs
class Solution: def getKthFromEnd(self, head: ListNode, k: int) -> ListNode: a_stack = [] ptr = head while ptr: #链表所有元素入栈 a_stack.append(ptr) ptr = ptr.next for i in range(k): #栈顶 k 个元素出栈 ptr = a_stack.pop() return ptr

时空复杂度#


设表长为 n,遍历整个链表得到时空复杂度 O(n) = n。注意此时虽然时间复杂度和蛮力法一样,但是 T(n) = n + k 比蛮力法低。
由于需要一个栈结构作为辅助空间,空间复杂度为 O(n)。

递归法#

方法思路#

获得倒数第 k 个结点,可以认为是遍历链表后通过回溯取到第 k 个元素。此时可以使用递归遍历完整个链表,然后回溯 k 次得到倒数第 k 个结点,接着再将这个结点传递回去。

题解代码#

Copy Highlighter-hljs
class Solution: num = 0 #全局变量记录回溯了几次 def fun(self, head: ListNode, k: int) -> ListNode: if head.next == None: #遍历完链表,开始回溯 Solution.num = Solution.num + 1 return head ptr = self.fun(head.next, k) Solution.num = Solution.num + 1 if Solution.num > k: #回溯次数 k 次 return ptr #传递倒数第 k 个结点 else: return head #返回递归层次所在结点 def getKthFromEnd(self, head: ListNode, k: int) -> ListNode: Solution.num = 0 #力扣需要手动初始化全局变量 return self.fun(head, k)

时空复杂度#

设表长为 n,需要遍历整个链表,然后回溯 n 次。得出 T(n) = 2n,进而得到 O(n) = n。
由于递归需要额外的辅助空间,空间复杂度为 O(n)。

参考资料#

《剑指 Offer(第2版)》,何海涛 著,电子工业出版社
双指针,栈,递归3种解决方式,有两种击败了100%的用户

posted @   乌漆WhiteMoon  阅读(100)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2020-04-17 Python 面向对象编程
点击右上角即可分享
微信分享提示
CONTENTS