HJ48 从单向链表中删除指定值的节点

357 阅读5分钟

Day13 2023/01/20

题目链接

难度:简单

题目

输入一个单向链表和一个节点的值,从单向链表中删除等于该值的节点,删除后如果链表中无节点则返回空指针。

链表的值不能重复。

构造过程,例如输入一行数据为:

6 2 1 2 3 2 5 1 4 5 7 2 2

则第一个参数6表示输入总共6个节点,第二个参数2表示头节点值为2,剩下的2个一组表示第2个节点值后面插入第1个节点值,为以下表示:

1 2 表示为

2->1

链表为2->1

3 2表示为

2->3

链表为2->3->1

5 1表示为

1->5

链表为2->3->1->5

4 5表示为

5->4

链表为2->3->1->5->4

7 2表示为

2->7

链表为2->7->3->1->5->4

最后的链表的顺序为 2 7 3 1 5 4

最后一个参数为2,表示要删掉节点为2的值

删除 结点 2

则结果为 7 3 1 5 4

数据范围:链表长度满足 1 ≤ n ≤ 1000  ,节点中的值满足0 ≤ va ≤ 10000 

测试用例保证输入合法

输入描述:

输入一行,有以下4个部分:

1 输入链表结点个数
2 输入头结点的值
3 按照格式插入各个结点
4 输入要删除的结点的值

输出描述:

输出一行

输出删除结点后的序列,每个数后都要加空格

示例

输入:5 2 3 2 4 3 5 2 1 4 3
输出:2 5 4 1
说明:
形成的链表为2->5->3->4->1
删掉节点3,返回的就是2->5->4->1  

思路一


本题目的关键在于如何构建链表,利用链表的一些基本操作即可完成,每次插入的时候遍历找到插入节点的前驱节点即可进行节点的插入。至于删除节点操作不清楚的可以看这篇:链接

思路二


在思路一的基础上,可以用数组模拟链表,其中插入(insert, push_bakck)和删除(erase)以及寻找前驱节点(find)都有对应的库函数,可以简化代码。

关键点


  • 要理解题目输入数据中所描述的插入节点的要求,例如:1 2 表示为 2->1,链表为2->1,即 1 为插入节点,2为插入节点的前驱节点,每次插入元素前,都要找到该前驱节点,一定要理解这一点,才能看懂代码是如何实现的。插入操作动图

75A0D2FCDC2A1BEA861BFF4DD9F357FC.gif

  • push_back(val)在数组末尾插入一个元素,val为插入值
  • insert(index, val)在数组指定位置插入一个元素,index为数组下标,val为插入元素。
  • erase(index)删除指定位置的元素。
  • find(begin, end, val) 在指定范围内(begin ~ end)查找元素值为val的元素,找到了返回元素下标,没有则返回end值。

算法实现


c++代码实现1-链表的基本操作

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

//方法一:使用链表的基本操作
//定义链表的节点
typedef struct LNode {
    int data;
    struct LNode* next;
    LNode(int data) : data(data), next(nullptr){};
} * LinkList;

int main() {
    int num, data; // num节点总数,data头节点数值
    int aft, pre; // aft待插入节点数值,pre插入节点的前驱节点数值
    cin >> num  >> data;
    LNode* L = new LNode(data); //创建头节点
    while (--num) {
        cin >> aft >> pre;
        LNode* newNode = new LNode(aft); // 创建待插入节点
        // 查找前驱节点,并插入
        LNode* cur = L; // 遍历指针,防止污染头指针
        while (cur) {
            if (cur->data == pre) { //插入节点
                newNode->next = cur->next;
                cur->next = newNode;
                break;
            } else
                cur = cur->next;
        }
    }
    //删除指定值节点
    int del;
    cin >> del;
    LNode* dummyHead = new LNode(0); //创建虚拟头节点,统一删除操作
    dummyHead->next = L;             //成为链表第一个节点
    LNode* cur = dummyHead;
    while (cur->next) {//防止cur->next->data这句报错
        if (cur->next->data == del) { //删除操作
            LNode* tmp = cur->next;   //暂存节点,方便释放
            cur->next = cur->next->next;
            delete tmp; //释放删除节点
        } else
            cur = cur->next;
    }
    delete dummyHead; //释放虚拟头节点
    LNode* tra = L;
    while (tra) { //遍历打印
        cout << tra->data << " ";
        tra = tra->next;
    }
    return 0;
}
  • 时间复杂度 O(n2)O(n^2) --- 插入节点需要遍历n-1次,其中寻找前驱节点也需要遍历指针,其中n为链表长度。
  • 空间复杂度 O(1)O(1) --- 链表空间属于必要空间,不属于额外空间

c++代码实现2-数组模拟

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

//方法二:数组模拟
int main() {
    int num, data; // num节点总数,data头节点数值
    int aft, pre; // aft待插入节点数值,pre插入节点的前驱节点数值
    cin >> num >> data;
    vector<int> res; //创建一个数组,模拟链表
    res.push_back(data);      //将表头数值放在数组开头
    while (--num) {
        cin >> aft >> pre;
        auto index = find(res.begin(), res.end(), pre); //找到前驱节点的下标
        if (index == res.end()) //当前驱节点是数组最后一个
            res.push_back(aft); //插入到数组末尾
        else
            res.insert(++index, aft); //插入到前驱节点的下一个位置
    }
    //删除指定值节点
    int del;
    cin >> del;
    res.erase(find(res.begin(), res.end(), del));
    //遍历打印
    for (auto it = res.begin(); it != res.end(); it++) {
        cout << *it << " ";
    }
    return 0;
}
  • 时间复杂度 O(n2)O(n^2) --- 插入节点需要遍历n-1次,find函数复杂度为O(n)
  • 空间复杂度 O(1)O(1) --- 数组属于必要空间,无额外的空间

总结

  • 一定要熟悉基本数据结构的一些基本操作。

  • 对于一些常见库函数的复杂度以及内部实现方式要有所了解。