HJ51 输出单向链表中倒数第k个结点

153 阅读3分钟

Day06 2023/01/13

题目链接

难度:简单

题目

输入一个单向链表,输出该链表中倒数第k个结点,链表的倒数第1个结点为链表的尾指针。

链表结点定义如下:

struct ListNode
{
    int data; //数据域
    ListNode* next; //指针域
};

正常返回倒数第k个结点指针,异常返回空指针.

要求:

(1)正序构建链表;

(2)构建后要忘记链表长度。

数据范围:链表长度满足 1 ≤ n ≤ 1000 ,链表中数据满足 0 ≤ data ≤ 10000

示例

输入:8 //链表节点个数
     1 2 3 4 5 6 7 8 //链表元素
     4 //倒数第几个节点
     
输出:5 //倒数第k个节点的数据

思路


由于本题的要求构建后要忘记链表长度,所以这里只能使用快慢双指针法,准备快慢双指针,都从链表头出发,快指针先行k步(指向第k+1个元素),达不到k步说明链表过短,返回空链表。然后快慢指针同步向后遍历,当快指针指向尾节点的时候停止,此时,快慢指针相差 k个元素,即慢指针正好指向倒数第 k 个元素, 如图所示:
411BAC4824C77353B52BB1434925F792.gif

关键点


  • 由于题目要求 正序构建链表,所以这里我们要使用尾插法,为了统一插入操作,可以选择带虚拟头节点的方式。

算法实现


c++代码实现-快慢指针法

#include <iostream>

using namespace std;

//定义链表节点
typedef struct LNode{
    int data; //数据域
    struct LNode* next; //指针域
    LNode(int x) : data(x), next(nullptr){}; //定义一个含参构造函数,next初始指向nullptr
}*LinkList; //LinkList强调链表,LNode强调节点

//根据题目要求这里采用带头结点的尾插法建立单链表,(返回头指针)
LinkList listTailInsert(int num){ //num为链表的节点个数
    LNode* dummyHead = new LNode(0); //创建一个虚拟头节点,不存储数据, 只是为了统一操作
    LNode* r = dummyHead; //创建尾指针 ,初始指向虚拟头节点
    LNode* s; //待插入元素
    int x; //元素数据
    int i = 1; //辅助打印的变量
    while(num > 0){ 
        cout << "请输入第"<< i++ << "个链表元素: " << endl;
        cin >> x;
        num--;
        s = new LNode(x); //创建新节点,并赋值数据
        r->next = s; //插入节点
        r =s ; //尾指针始终指向当前尾节点
    }
    cout << "链表创建完毕!" << endl;
    LNode* L = dummyHead->next; //真正的头指针为虚拟头结点的下一个节点
    delete dummyHead; //链表创建完毕释放虚拟头节点所占内存
    return L; //返回头指针
}

//输出链表倒数第k个节点
LNode* recNode(int k, LNode* L) { //k倒数第几个链表节点 , L为链表的头指针
    LNode* fast = L; //快指针
    LNode* slow = L; //慢指针
    
    //先让fast指针先行k步
    for(int i = 0; i < k; i++) { 
        if(fast != nullptr) fast = fast->next;
        else return nullptr; //非法k值,返回空指针
    }

    //快慢指针同步,当快指针指向尾节点下一个节点时停止
    while (fast != nullptr) {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;//返回倒数第k个元素的指针
}

int main() {
    cout << "请输入链表节点个数:";
    int num = 0; //节点个数
    cin >> num;
    LNode* L = listTailInsert(num); //创建链表,返回头指针

    cout << "请输入你要返回倒数第几个链表节点:";
    int k = 0;
    cin >> k;
   
   //测试一下
   LNode* tarNode = recNode(k, L);
   cout << "倒数第4个元素为:" << tarNode->data << endl;
}
  • 时间复杂度 O(n)O(n) ---快慢指针遍历单链表,其中 n 为单链表长度
  • 空间复杂度 O(1)O(1) --- 不考虑创建单链表所占用的空间

总结

  • 要牢记快慢指针法,在有关数组,链表的问题中,经常用到。

  • 快慢指针法还可以降低时间复杂度,实现在一个循环内完成两层嵌套循环的工作。

  • 虽然这里考察的算法解题的能力,但不要忽略单链表节点的定义和创建这些基本的操作用代码是如何实现的。