面试题 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. 通用性:这种技巧可以推广到许多类似问题

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

相关推荐
CoderJia程序员甲2 小时前
GitHub 热榜项目 - 日榜(2026-01-31)
ai·开源·大模型·github·ai教程
布谷歌2 小时前
面试题整理
java·开发语言
2401_859049082 小时前
git submodule update --init --recursive无法拉取解决
前端·chrome·git
j445566112 小时前
C++中的职责链模式高级应用
开发语言·c++·算法
Hello World . .2 小时前
数据结构:栈和队列
c语言·开发语言·数据结构·vim
jjjava2.02 小时前
深入解析Set与Map的奥秘
java·开发语言
白宇横流学长2 小时前
基于Java的火车票订票系统的设计与开发
java·开发语言
黎雁·泠崖2 小时前
Java核心基础API学习总结:从Object到包装类的核心知识体系
java·开发语言·学习
Yvonne爱编码2 小时前
JAVA数据结构 DAY1-集合和时空复杂度
java·数据结构·python