面试题 02.02. 返回倒数第 k 个节点 - 题解与详细分析

题目描述

实现一种算法,找出单向链表中倒数第 k 个节点,返回该节点的值。

注意: 本题相对原题稍作改动

示例

复制代码
输入:1->2->3->4->5 和 k = 2
输出:4

说明:

  • 给定的 k 保证是有效的

解题思路

这道题要求找到链表中倒数第 k 个节点。最直观的解法是使用双指针技巧(快慢指针),这种方法只需要一次遍历即可解决问题。

双指针法原理

  1. 快指针先移动 k 步

  2. 慢指针从链表头开始

  3. 然后两个指针同时移动,当快指针到达链表末尾时,慢指针正好指向倒数第 k 个节点

为什么这样有效?

  • 快指针领先慢指针 k 步

  • 当快指针到达末尾(NULL)时,慢指针距离末尾正好 k 步

  • 因此慢指针指向的就是倒数第 k 个节点

代码实现

复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
int kthToLast(struct ListNode* head, int k) {
    struct ListNode* fast = head;  // 快指针
    struct ListNode* cur = head;   // 慢指针(当前指针)
    
    // 快指针先移动 k 步
    while(k--)
    {
        fast = fast->next;
    }

    // 两个指针同时移动,直到快指针到达末尾
    while(fast)
    {
        cur = cur->next;
        fast = fast->next;
    }
    
    return cur->val;  // 返回倒数第 k 个节点的值
}

代码详解

核心逻辑

复制代码
// 快指针先移动 k 步
while(k--)
{
    fast = fast->next;
}

// 两个指针同时移动
while(fast)
{
    cur = cur->next;
    fast = fast->next;
}

执行过程可视化

以链表 1->2->3->4->5k = 2 为例:

复制代码
初始状态:
fast -> 1, cur -> 1

快指针先移动 2 步:
第1步:fast -> 2
第2步:fast -> 3

现在:
fast -> 3, cur -> 1

两个指针同时移动:
第1次:fast -> 4, cur -> 2
第2次:fast -> 5, cur -> 3
第3次:fast -> NULL, cur -> 4

返回 cur->val = 4

边界情况分析

k = 1(倒数第一个节点)

复制代码
链表:1->2->3->4->5, k = 1

快指针先移动 1 步:fast -> 2
然后同时移动:
fast -> 3, cur -> 2
fast -> 4, cur -> 3
fast -> 5, cur -> 4
fast -> NULL, cur -> 5

返回 5(最后一个节点)

k = 链表长度(倒数第 n 个节点,即第一个节点)

复制代码
链表:1->2->3->4->5, k = 5

快指针先移动 5 步:fast -> NULL
然后同时移动:不进入循环(因为 fast 已经是 NULL)

返回 cur->val = 1(第一个节点)

其他解法

方法二:两次遍历法

复制代码
int kthToLast(struct ListNode* head, int k) {
    // 第一次遍历:计算链表长度
    int length = 0;
    struct ListNode* current = head;
    while (current != NULL) {
        length++;
        current = current->next;
    }
    
    // 第二次遍历:找到第 (length - k) 个节点
    current = head;
    for (int i = 0; i < length - k; i++) {
        current = current->next;
    }
    
    return current->val;
}

优缺点:

  • 优点:思路简单直观

  • 缺点:需要遍历链表两次

方法三:递归法

复制代码
int count = 0;
int result = 0;

int kthToLast(struct ListNode* head, int k) {
    if (head == NULL) {
        return 0;
    }
    
    kthToLast(head->next, k);
    count++;
    
    if (count == k) {
        result = head->val;
    }
    
    return result;
}

优缺点:

  • 优点:代码简洁

  • 缺点:使用递归栈,空间复杂度 O(n)

复杂度分析

双指针法

  • 时间复杂度:O(n),只需遍历链表一次

  • 空间复杂度:O(1),只使用两个指针变量

比较其他方法

  • 两次遍历法:时间复杂度 O(n),空间复杂度 O(1)

  • 递归法:时间复杂度 O(n),空间复杂度 O(n)(递归栈)

扩展思考

如果要求返回节点而不是节点的值

复制代码
struct ListNode* getKthFromEnd(struct ListNode* head, int k) {
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    
    // 快指针先走 k 步
    while (k-- > 0) {
        fast = fast->next;
    }
    
    // 同时移动直到快指针到达末尾
    while (fast != NULL) {
        slow = slow->next;
        fast = fast->next;
    }
    
    return slow;
}

如果 k 可能无效

复制代码
int kthToLast(struct ListNode* head, int k) {
    // 检查 k 的有效性
    if (k <= 0) return -1; // 或者适当的错误处理
    
    struct ListNode* fast = head;
    struct ListNode* cur = head;
    
    // 快指针先移动 k 步,同时检查是否超出链表长度
    while (k-- > 0) {
        if (fast == NULL) {
            return -1; // k 大于链表长度
        }
        fast = fast->next;
    }

    // 两个指针同时移动
    while (fast != NULL) {
        cur = cur->next;
        fast = fast->next;
    }
    
    return cur->val;
}

应用场景

这种快慢指针技巧在很多场景中都有应用:

  1. 找到链表的中间节点(快指针每次两步,慢指针每次一步)

  2. 判断链表是否有环

  3. 找到环的入口点

  4. 删除链表的倒数第 N 个节点

总结

这道题展示了双指针技巧的又一个经典应用:

  1. 核心思想:快指针领先慢指针 k 步,当快指针到达末尾时,慢指针正好在倒数第 k 个位置

  2. 效率优势:只需一次遍历,时间复杂度 O(n),空间复杂度 O(1)

  3. 边界处理:题目保证 k 有效,所以不需要额外检查

  4. 通用性:这种技巧可以推广到许多类似问题

掌握这种双指针技巧对于解决链表相关问题非常有帮助,是面试中常见的考点之一。

相关推荐
Wpa.wk几秒前
Git日志+分支管理+基础冲突解决
经验分享·git·测试工具
我是唐青枫7 分钟前
C#.NET Span 深入解析:零拷贝内存切片与高性能实战
开发语言·c#·.net
lxh011316 分钟前
数据流的中位数
开发语言·前端·javascript
盒马盒马24 分钟前
Rust:迭代器
开发语言·后端·rust
Full Stack Developme1 小时前
Java 常用通信协议及对应的框架
java·开发语言
前端DOM哥2 小时前
GitHub 热榜 Top 10 🔥(3·15)
github
铁手飞鹰2 小时前
Visual Studio创建Cmake工程导出DLL,通过Python调用DLL
android·python·visual studio
飞Link3 小时前
告别盲目找Bug:深度解析 TSTD 异常检测中的预测模型(Python 实战版)
开发语言·python·算法·bug
1.14(java)3 小时前
Spring-boot快速上手
java·开发语言·javaee
djarmy3 小时前
ubuntu20.04搭建openharmony6.0的master分支。构建编译环境报错解决记录
c语言·ubuntu