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

174 阅读3分钟

描述

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

链表结点定义如下:

struct ListNode
{
    int m_nKey;
    ListNode* m_pNext;
};

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

要求:

(1)正序构建链表;

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

数据范围:链表长度满足 1 \le n \le 1000 \1≤n≤1000 , k \le n *kn* ,链表中数据满足 0 \le val \le 10000 \0≤val≤10000

本题有多组样例输入。

输入描述:

输入说明 1 输入链表结点个数 2 输入链表的值 3 输入k的值

输出描述:

输出一个整数

示例1

输入:

8
1 2 3 4 5 6 7 8
4

复制

输出:

5

具体思路

  • 法1:快慢指针法,设置快指针和慢指针,快指针比满指针快k步,当快指针遍历到尾结点的时候,满指针得位置便是倒数第k个结点

    alt

    • 法2:头插建表法,对于逆向输出的数据,都可以采用头插法建立新表的方式逆向输出

    • 法3:对于逆序的题目,都可以使用递归法

具体实现

#include <math.h>
#include <algorithm>
#include <iostream>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <stdlib.h>
#include <cstdlib>
#include <cstring>

//#include "./include/list/sqlist.h"

using namespace std;

typedef struct ListNode{
    int data;
    struct ListNode *next;
    /* data */
}ListNode, *LinkList;

//day6
//王道第21题
//蓝蓝星球打卡题目,输入一个单向链表,输出该链表中倒数第k个结点,链表的倒数第1个结点为链表的尾指针。
//尾插法建立链表
/**
 * @description: 
 * @param {LinkList} &L
 * @param {int} n
 * @return {*}
 */
LinkList ListTailInsert(LinkList &L, int n){
    L = (LinkList)malloc(sizeof(ListNode));//创建头结点
    ListNode* s;//创建临时结点
    ListNode* r = L;//创建尾结点
    int x;
    //L->next = NULL;
    //scanf("%d",&x);
    while(n--){
        scanf("%d",&x);
        s=(ListNode*)malloc(sizeof(ListNode));
        s->data = x;
        r->next = s;
        r = s;
        //scanf("%d",&x);
    }
    r->next = NULL;
    return L;
}

//打印单链表
void PrintLinkedList(LinkList& L){
    ListNode* p = L->next;//头结点
    while(p!=NULL){
        std::cout << p->data << "--->" << "\a";
        p = p->next;//下个结点
    }
    std::cout << std::endl;
}


//法2:设置p,q两个工作指针,p为快指针,先移动k位,然后再同步移动q指针,当p到尾部时,q处于的位置是倒数第k个
/**
 * @description: 时间复杂度O(n),空间复杂度O(1)
 * @param {LinkList &} L
 * @param {int} k
 * @return {*}
 */
ListNode*  FindKInListII(LinkList & L, int k ){
    //第一步设置快慢指针
    ListNode *p = L->next, *q = L->next;
    int count = 0;//计数,当count==k的时候p才移动
    while(q != NULL){
        if(count < k) count++;
        else p = p->next;
        q = q->next;
    }
    if(count < k) return NULL;
    else{
        return p;
    }
}

//法1:头插建表法
/**
 * @description: 时间复杂度O(n) 空间复杂度O(n)
 * @param {LinkList} L
 * @param {int} k
 * @return {*}
 */
ListNode* FindKInList(LinkList L,int k){
    LinkList L1 = (LinkList) malloc(sizeof(ListNode));
    //L1 = L;//将L的头结点赋给L1,作为L1的新头结点
    //L1->next = NULL;//新链表
    ListNode *p = L->next, *q;//工作结点和临时结点
    L->next = NULL;
    //使用头插法依次插入到L1
    while(p != NULL){
        //q = (ListNode*) malloc(sizeof(ListNode));
        //q = p;//先赋值
        q = p->next;
        p->next = L->next;
        L->next = p;
        p = q;//继续遍历
    }
    //第二部分就是在新链表中直接查找第k个元素
    ListNode* p1 = L->next;//新链表的元素
    //PrintLinkedList(p1);
    while(p1 != NULL && k != 0){
        q = p1;
        p1 = p1->next;
        k--;
    }
    if(k != 0) return NULL;
    else{
        return q;
    }
}

//法3:递归法
/**
 * @description: 时间复杂度O(n),空间复杂度O(n)额外的栈空间
 * @param {LinkList} L
 * @param {int &} index
 * @return {*}
 */
ListNode* FindKInListIII(LinkList L, int & index){
    //终止条件
    if(L == NULL) return NULL;
    //做点什么
    //先遍历下一个
    ListNode *p = FindKInListIII(L->next, index);
    if(--index == 0) return L;
    else return p;
}

int main(){


    //day06
    LinkList L;
    int n;//输入数据数量
    int index = 0;//输入的位置
    ListNode* p;
    while(scanf("%d",&n) != EOF){
        L = ListTailInsert(L,n);
        scanf("%d",&index);
        if(index == 0) std::cout << 0 << std::endl;
        else{
            p = FindKInListIII(L,index);
            if(p != NULL) printf("%d\n",p->data);
        }
        //PrintLinkedList(L);
    }
    system("pause");
    return 0;
}

image.png

时间复杂度

  • 法1:时间复杂度:O(n),空间复杂度:O(1)
  • 法2:时间复杂度:O(n),空间复杂度:O(n)
  • 法3:时间复杂度:O(n),空间复杂度:O(N)

小结

对比于这类题目,法2头插建表法属于暴力破解法,如果想不到快慢指针法可以采用这类做法

法1才是最优解,空间复杂度为O(1)