《剑指 Offer》学习记录:题 22:链表中倒数第 k 个结点
题 22:链表中倒数第 k 个结点
题干
输入一个链表,输出该链表中倒数第 k 个结点。为了符合大多数人的习惯,本题从 1 开始计数,即链表的尾结点是倒数第 1 个结点。例如一个链表有 6 个结点,从头结点开始它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个结点是值为 4 的结点。——《剑指 Offer》P134
测试样例
链表的数据结构定义如下(Python):
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
若传入的头结点 head 指向的链表结构如图所示:
要求返回倒数第 4 个结点,也就是结点 2:
蛮力法
方法思路
蛮力法的思路比较简单,设表长为 n,为了得到第 n - k 个结点,就需要先获得表长。首先先遍历一遍链表求出表长,然后再遍历一次链表得到第 n - k 个结点。
题解代码
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 个结点。
题解代码
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 个元素就能得到需要的结点。
题解代码
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 个结点,接着再将这个结点传递回去。
题解代码
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%的用户