剑指offer-14、链表中倒数第k个结点

题⽬描述

输⼊⼀个链表,输出该链表中倒数第k个结点。

例如输⼊{1,2,3,4,5} , 2 时,对应的链表结构如下图所示:

其中蓝⾊部分为该链表的最后2 个结点,所以返回倒数第2 个结点(也即结点值为4 的结点)即可,系统会打印后⾯所有的节点来⽐较。

示例1 输⼊:{1,2,3,4,5},2 返回值:{4,5} 说明:返回倒数第2个节点4,系统会打印后⾯所有的节点来⽐较。

示例2 输⼊:{2},8 返回值:{}

思路及解答

两次遍历法

  1. 第一次遍历计算链表长度n
  2. 第二次遍历到第n-K+1个节点(即倒数第K个节点)
  3. 如果K大于链表长度,返回null
java 复制代码
public ListNode findKthToTail(ListNode head, int k) {
    if (head == null || k <= 0) return null;
    
    // 第一次遍历计算链表长度
    int length = 0;
    ListNode current = head;
    while (current != null) {
        length++;
        current = current.next;
    }
    
    // 检查k是否有效
    if (k > length) return null;
    
    // 第二次遍历找到目标节点
    current = head;
    for (int i = 0; i < length - k; i++) {
        current = current.next;
    }
    
    return current;
}
  • 时间复杂度:O(n),需要遍历链表两次
  • 空间复杂度:O(1),只使用了固定数量的指针

双指针法(推荐)

快慢双指针,先让第1 个指针先⾛k 步,然后第2 个指针开始⾛,⽽且两个指针⼀起⾛,直到第⼀个指针⾛到最后的位置。

  1. 使用快慢两个指针,快指针先移动K步
  2. 然后两个指针同步移动,当快指针到达末尾时,慢指针正好指向倒数第K个节点
  3. 如果快指针在移动K步前到达末尾,说明K大于链表长度
java 复制代码
public ListNode findKthToTail(ListNode head, int k) {
    if (head == null || k <= 0) return null;
    
    ListNode fast = head;
    ListNode slow = head;
    
    // 快指针先移动k步
    for (int i = 0; i < k; i++) {
        if (fast == null) return null; // k大于链表长度
        fast = fast.next;
    }
    
    // 同步移动两个指针
    while (fast != null) {
        fast = fast.next;
        slow = slow.next;
    }
    
    return slow;
}
  • 时间复杂度:O(n),只需遍历链表一次
  • 空间复杂度:O(1),使用了两个指针

栈辅助法(空间换时间)

  1. 将所有节点压入栈
  2. 弹出K个节点,最后一个弹出的即为所求
  3. 如果栈中节点不足K个,返回null
java 复制代码
public ListNode findKthToTail(ListNode head, int k) {
    if (head == null || k <= 0) return null;
    
    Stack<ListNode> stack = new Stack<>();
    ListNode current = head;
    
    // 所有节点入栈
    while (current != null) {
        stack.push(current);
        current = current.next;
    }
    
    // 检查k是否有效
    if (k > stack.size()) return null;
    
    // 弹出k个节点
    ListNode result = null;
    for (int i = 0; i < k; i++) {
        result = stack.pop();
    }
    
    return result;
}
  • 时间复杂度:O(n),需要遍历链表两次(入栈和出栈)
  • 空间复杂度:O(n),需要额外栈空间存储所有节点

递归回溯法

  1. 递归遍历到链表末尾
  2. 回溯时计数,当计数等于K时返回当前节点
  3. 使用全局变量或包装类传递计数
java 复制代码
private int count = 0;

public ListNode findKthToTail(ListNode head, int k) {
    if (head == null) return null;
    
    ListNode node = getKthFromEnd(head.next, k);
    count++;
    
    if (count == k) {
        return head;
    }
    return node;
}
  • 时间复杂度:O(n),需要完整遍历链表
  • 空间复杂度:O(n),递归栈空间开销

方法对比与总结

方法 时间复杂度 空间复杂度 优点 缺点
两次遍历法 O(n) O(1) 实现简单 需要两次遍历
双指针法 O(n) O(1) 一次遍历,效率高 边界条件需仔细处理
栈辅助法 O(n) O(n) 实现直观 空间开销大
递归回溯法 O(n) O(n) 展示递归思想 空间效率低
相关推荐
白露与泡影1 小时前
2025年高质量Java面试真题汇总
java·python·面试
꒰ঌ 安卓开发໒꒱1 小时前
Java 面试 -Java基础
java·开发语言·面试
菠菠萝宝1 小时前
【Java八股文】13-中间件面试篇
java·docker·kafka·rabbitmq·canal·rocketmq·es
瓯雅爱分享3 小时前
基于Java后端与Vue前端的MES生产管理系统,涵盖生产调度、资源管控及数据分析,提供全流程可视化支持,包含完整可运行源码,助力企业提升生产效率与管理水平
java·mysql·vue·软件工程·源代码管理
hello_2505 小时前
k8s安全机制解析:RBAC、Service Account与安全上下文
java·安全·kubernetes
望获linux6 小时前
【实时Linux实战系列】实时安全 C++ 模式:无异常、预分配与自定义分配器
java·linux·服务器·开发语言·数据库·chrome·tomcat
多多*6 小时前
linux安装hbase(完)
java·分布式·算法·c#·wpf
new_daimond6 小时前
设计模式-享元模式详解
java·设计模式·享元模式
软件柠檬7 小时前
Java中Integer是如何应用享元模式的?
java·享元模式
Taylor不想被展开7 小时前
SpringBoot 项目集成 Flyway
java·spring boot·mysql