【每日一题】2009考研数据结构 - 求倒数第k个数的值

已知一个带有表头结点的单链表,结点结构为 datalink。假设该链表只给出了头指针 list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第 k 个位置上的结点(k 为正整数)。

要求:

  1. 若查找成功,算法输出该结点的 data 域的值,并返回 1
  2. 若查找失败,则返回 0

题目分析与设计思想

给定一个单链表,我们的目标是找到链表中倒数第 k 个结点的数据值。在链表中,由于无法通过下标直接访问倒数第 k 个元素,我们需要采取其他方法。最简单的方法是遍历两次链表:第一次获取链表的长度,第二次找到倒数第 k 个结点。然而,这种方法的时间复杂度为 O(n + n) = O(2n),可以进一步优化。

为了提高效率,我们可以使用双指针法,先让一个指针先走 k 步,然后两个指针一起移动,直到第一个指针到达链表末尾时,第二个指针刚好指向倒数第 k 个节点。这样实现的算法只需遍历一次链表,时间复杂度为 O(n)


实现步骤

  1. 初始化两个指针 leftcur,都指向链表头部。
  2. cur 指针向前移动 k 步。如果在这过程中 cur 指针提前到达链表末尾,说明链表长度不足 k,直接返回 0
  3. cur 指针顺利移动 k 步且没有到达链表末尾,则继续让 leftcur 两个指针同时移动,直到 cur 指针到达链表末尾。
  4. 此时,left 指针指向的结点即为倒数第 k 个结点,输出该结点的 data 值并返回 1

代码实现(双指针)

以下是代码实现,并附有详细注释:

#include "bits/stdc++.h"
using namespace std;

// 定义链表节点结构
struct Node {
    
    
    int data;      // 节点数据
    Node* next;    // 指向下一个节点
};

// 创建新节点的辅助函数
Node* createNode(int data) {
    
    
    Node* node = new Node();  // 分配内存给新节点
    node->data = data;        // 设置节点数据
    node->next = nullptr;     // 初始化下一节点为null
    return node;
}

// 查找倒数第k个节点的主函数
int getKData(Node* head, int k) {
    
    
    Node* left = head; // 第一个指针
    int t = k;
    Node* cur = head;  // 第二个指针

    // cur指针向前移动k步
    while (t-- && cur != nullptr) {
    
    
        cur = cur->next;
    }
    
    // 如果链表长度小于k,则返回0
    if (t != -1) return 0;

    // 两个指针一起移动
    while (cur != nullptr) {
    
    
        cur = cur->next;
        left = left->next;
    }
    return left->data;
}

int main() {
    
    
    // 创建测试链表
    Node* head = createNode(1);
    Node* cur = head;
    for (int i = 2; i <= 7; i++) {
    
    
        Node* newNode = createNode(i);
        cur->next = newNode;
        cur = newNode;
    }
    
    // 打印链表
    cur = head; // 从头节点开始
    while (cur != nullptr) {
    
    
        cout << cur->data << " "; // 打印当前节点的数据
        cur = cur->next; // 移动到下一个节点
    }
    cout << endl;
    
    // 查找倒数第2个节点
    int value = getKData(head, 2);
    cout << value << endl; // 输出结果
    
    return 0;
}

代码解析

1. 创建链表

main 函数中,我们首先创建一个单链表作为测试用例,包含从 17 的数据结点。

2. 双指针查找倒数第 k 个节点

getKData 函数中,我们通过移动 cur 指针 k 步来预留倒数位置。随后将 curleft 指针同时移动,直至 cur 指向 nullptr。此时 left 就指向了倒数第 k 个节点。

时间复杂度与空间复杂度分析

  • 时间复杂度O(n),因为最多需要遍历链表一次。
  • 空间复杂度O(1),因为只使用了常数空间来存储两个指针。

代码实现(两次遍历)

// 查找倒数第k个节点的两次遍历法实现
int getKDataTwoPass(Node* head, int k) {
    
    
    Node* cur = head;
    int length = 0;

    // 第一次遍历:计算链表长度
    while (cur != nullptr) {
    
    
        length++;
        cur = cur->next;
    }

    // 如果k大于链表长度,返回0
    if (k > length) return 0;

    // 计算倒数第k个节点的位置(从头开始的正数位置)
    int targetIndex = length - k;

    // 第二次遍历:找到正数第targetIndex个节点
    cur = head;
    for (int i = 0; i < targetIndex; i++) {
    
    
        cur = cur->next;
    }

    return cur->data;
}

代码讲解

  1. 第一次遍历:遍历整个链表,计算链表的总长度 length
  2. 长度检查:判断 k 是否大于链表的长度。如果是,则返回 0,因为倒数第 k 个节点不存在。
  3. 计算目标位置:根据总长度和 k 值计算目标节点的正数位置 targetIndex = length - k
  4. 第二次遍历:重新从链表头部开始遍历,到达 targetIndex 位置时停止,即为倒数第 k 个节点,返回该节点的 data

时间复杂度

  • 时间复杂度O(n),需要两次遍历链表,每次遍历的时间复杂度为 O(n),因此整体复杂度为 O(n + n) = O(2n)
  • 空间复杂度O(1),只使用了常数空间来存储变量。

方法比较

  1. 双指针法

    • 优点:只需遍历一次链表,时间复杂度为 O(n),更高效。
    • 缺点:需要多一点的代码逻辑处理。
  2. 两次遍历法

    • 优点:逻辑简单,第一次遍历获取链表长度,第二次定位倒数位置。
    • 缺点:时间复杂度为 O(2n),较慢。

在这里插入图片描述

总结

本题通过使用双指针法,避免了两次遍历链表,实现了更高效的倒数第 k 个结点查找。希望本文的详细解析帮助大家更好地理解链表操作和双指针的用法。该方法是一种在链表中查找倒数位置的常用技巧,对理解链表的操作非常有帮助。

猜你喜欢

转载自blog.csdn.net/qq_37945670/article/details/143569533