zoukankan      html  css  js  c++  java
  • 《剑指 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%的用户

  • 相关阅读:
    linux软件安装
    shell脚本
    ssh密钥登录及远程执行命令
    shell编程
    vi编辑器
    linux入门
    《玩转Bootstrap(JS插件篇)》笔记
    SharePoint BI
    Apache-ActiveMQ transport XmlMessage
    C#操作AD及Exchange Server总结(二)
  • 原文地址:https://www.cnblogs.com/linfangnan/p/14670483.html
Copyright © 2011-2022 走看看