前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >手把手教你完整实现一个链表

手把手教你完整实现一个链表

作者头像
TechFlow-承志
发布于 2023-03-02 07:01:02
发布于 2023-03-02 07:01:02
25300
代码可运行
举报
文章被收录于专栏:TechFlowTechFlow
运行总次数:0
代码可运行

作者 | 梁唐

出品 | 公众号:Coder梁(ID:Coder_LT)

大家好,我是梁唐。

上一篇文章当中我们介绍了链表的原理以及一些基本操作,今天我们来实际练习一下,进一步加深一下印象。

我们先来看一道基础题,LeetCode 203,移除链表元素。

移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

分析

本题是链表应用的裸题,考察的是对链表删除操作的掌握。我们很容易就写出代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* pt = head;
        
        // 使用while循环遍历链表
        while (pt != nullptr && pt->next != nullptr) {
            if (pt->next->val == val) {
                pt->next = pt->next->next;
            // 注意这里的else
            }else pt = pt->next;
        }
        return head;
    }
};

注意一点小细节,由于链表的删除操作是pt->next = pt->next->next。即将当前的next指针指向再之后的一个元素,这就导致了我们每次判断的都是下一个元素是否要删除,而非当前元素。并且,在删除了下一个元素之后,虽然当前指针没有变化,但下一个元素已经变了,所以我们不需要移动指针了。

另外还有一个小问题:如果开头就是要删除的元素怎么办,我们没有比head更早的节点了,又怎么删除head呢?如果试着提交一下上面的代码, 就会发现无法通过头一个元素就需要删除的case。

针对这个问题,一种比较容易想到的方法是我们先对链表的头部进行特判,先将头部所有等于val的节点全部删除,找到新的head指针之后,再执行上面的操作。

这当然是没问题的,但解决这个问题,我们有更优雅的方法,并且这个方法的使用频率非常高,几乎在所有链表相关的问题中都可以使用。

这个方法就是创建一个虚拟的头节点,这个头节点是我们创建出来的,所以无论如何它都不会删除。所以不知道返回的头节点是什么的问题也就不存在了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* nd = new ListNode();
        nd->next = head;

        ListNode* pt = nd;
        
        while (pt != nullptr && pt->next != nullptr) {
            if (pt->next->val == val) {
                auto tmp = pt->next;
                pt->next = pt->next->next;
                delete tmp;
            }else pt = pt->next;
        }
        return nd->next;
    }
};

小试牛刀之后我们再来看一题:LeetCode 707 设计链表。

设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:valnextval 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

分析

在这道题当中我们要实现一个链表的类,提供题意当中列举的这些方法。

LeetCode当中这类的问题很多,平台已经为我们定义好了代码框架,我们只需要实现细节。这是一个了解和学习面向对象非常好的机会,强烈建议初学乍练的萌新不要错过。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyLinkedList {
public:
    MyLinkedList() {

    }
    
    int get(int index) {

    }
    
    void addAtHead(int val) {

    }
    
    void addAtTail(int val) {

    }
    
    void addAtIndex(int index, int val) {

    }
    
    void deleteAtIndex(int index) {

    }
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

首先声明,这道题因为涉及末尾插入元素以及指定下标添加、删除元素,涉及的细节更多,要比看起来的更困难一些。

观察代码可以发现只是替我们定义好了链表的类,没有链表中节点的相关定义,首先写好这个部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyLinkedList {
public:
    struct Node {
        int val;
        Node* next;
        Node(int x, Node* pt=nullptr): val(x), next(pt) {}
    };
...

题目中需要我们往末尾插入元素的操作,因此我们除了需要head之外,还需要一个tail记录最后一个元素。head是虚拟头节点,永远不变,而tail是尾节点,指向最后一个节点的位置。因此随着我们的操作,tail可能会发生变化。这一点非常重要,切记。

初始化时我们令tail = head,这样才能保证不论我们往头部还是尾部插入元素得到的结果相同。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Node *head, *tail;

MyLinkedList() {
    head = new Node(0);
    tail = head;
}

我非常推荐在开始编码之前先实现一个printAll函数,用来打印链表当中的所有元素。这样会比较方便我们debug

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void printAll() {
    auto pt = head;
    while (pt->next != nullptr) {
        cout << pt->next->val << endl;
        pt = pt->next;
    }
}

之后从易到难,我们先来实现最简单的从头部插入元素以及从末尾插入元素。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void addAtHead(int val) {
    head->next = new Node(val, head->next);
    // 注意移动tail
    if (head == tail) tail = head->next;
}

void addAtTail(int val) {
    tail->next = new Node(val, nullptr);
    // 移动tail
    tail = tail->next;
}

addAtTail很简单,我们在tail指针后面创建一个新的节点即可,但别忘了创建好了之后当前的tail就不是最后一个节点的位置了,要更新一下。

同样在addAtHead函数当中也有这个问题,刚开始的时候链表为空,往head后面添加元素就相当于往末尾添加。因此要加上特判,当head等于tail时,也要更新tail

接下来比较好实现的是get函数,需要返回指定下标的元素。我们只需要使用for循环从head开始移动index+1次,如果没到链表结尾的话,返回指向的元素即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int get(int index) {
    auto pt = head;
    for (int i = 0; i <= index; i++) {
        pt = pt->next;
        if (pt == nullptr) return -1;
    }
    return pt->val;
}

get没问题了之后,稍微修改一下就是addAtIndex函数。这里要小心,由于我们在插入元素的时候都会修改插入之前的节点,所以这里我们只需要移动index次,比上面要少一次。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void addAtIndex(int index, int val) {
    auto pt = head;
    for (int i = 0; i < index; i++) {
        pt = pt->next;
        if (pt == nullptr) return ;
    }
    pt->next = new Node(val, pt->next);
    if (pt == tail) tail = pt->next;
}

最后才是删除元素,基本上是插入元素的逆操作。除了小心空指针之外,还要注意如果删除的节点刚好是tail,要把tail往前移。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void deleteAtIndex(int index) {
    auto pt = head;
    for (int i = 0; i < index; i++) {
        pt = pt->next;
        if (pt == nullptr) return ;
    }
    auto tmp = pt->next;
    if (tmp == nullptr) return ;
    pt->next = pt->next->next;
    if (tmp == tail) tail = pt;
    delete tmp;
}

最后贴一下完整代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyLinkedList {
public:
    struct Node {
        int val;
        Node* next;
        Node(int x, Node* pt=nullptr): val(x), next(pt) {}
    };

    Node *head, *tail;

    MyLinkedList() {
        head = new Node(0);
        tail = head;
    }
    
    int get(int index) {
        auto pt = head;
        for (int i = 0; i <= index; i++) {
            pt = pt->next;
            if (pt == nullptr) return -1;
        }
        return pt->val;
    }
    
    void addAtHead(int val) {
        head->next = new Node(val, head->next);
        if (head == tail) tail = head->next;
    }
    
    void addAtTail(int val) {
        tail->next = new Node(val, nullptr);
        tail = tail->next;
    }
    
    void addAtIndex(int index, int val) {
        auto pt = head;
        for (int i = 0; i < index; i++) {
            pt = pt->next;
            if (pt == nullptr) return ;
        }
        pt->next = new Node(val, pt->next);
        if (pt == tail) tail = pt->next;
    }
    
    void deleteAtIndex(int index) {
        auto pt = head;
        for (int i = 0; i < index; i++) {
            pt = pt->next;
            if (pt == nullptr) return ;
        }
        auto tmp = pt->next;
        if (tmp == nullptr) return ;
        pt->next = pt->next->next;
        if (tmp == tail) tail = pt;
        delete tmp;
    }
};
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coder梁 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
LeetCode 707. 设计链表(List)
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。 addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。 addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。 addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。 deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
Michael阿明
2020/07/13
3310
LeetCode 707. 设计链表(List)
【手绘漫画】面试必考之手撕单链表(解题模板和深度剖析),(LeetCode 707)
单链表中的每个结点不仅包含值,还包含链接到下一个结点的引用字段。通过这种方式,单链表将所有结点按顺序组织起来。
我是管小亮
2020/04/20
4190
c++的链表-链表入门(C++)
  单链表与数组不同,数组中只存储元素的值,而单链表中除了数据的值外还包括了指向下一个节点的引用字段通常以next来表示。如下图表示,通过这个引用,单链表将所有节点按照顺序组织起来。
宜轩
2022/12/26
1.1K0
链表——707. 设计链表
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。 在链表类中实现这些功能:
向着百万年薪努力的小赵
2022/12/02
3020
链表——707. 设计链表
【代码随想录】二刷-链表
链表 《代码随想录》 前言 C++定义链表结点方式 struct node{ int val; struct node* next; node(int v):val,next(nullptr){} }; 与数组对比 下面题目大部分时间复杂度如上图为O(n) 203. 移除链表元素 头结点和非头结点分开判断删除 class Solution { public: ListNode* removeElements(ListNode* head, int val)
半生瓜的blog
2023/05/13
1970
【代码随想录】二刷-链表
LeetCode707:设计链表 Design Linked List
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
爱写bug
2019/07/14
3580
TypeScript算法题实战——链表篇(链表的设计、反转、两两交换、删除、相交和环形链表)
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思),链表的类型有单链表、双链表、循环链表。
中杯可乐多加冰
2024/08/03
1950
LeetCode——707 设计链表
这题其实很简单,这里的实现方式是单链表,基本上就是数据结构的知识点,所以没什么好提的。但是万恶的LeetCode有这么个测试样例:
太阳影的社区
2021/10/15
2000
链表:一道题目考察了常见的五个操作!
关于代码的一切尽在「代码随想录」 ❝听说这道题目把链表常见的五个操作都覆盖了?❞ 第707题:设计链表 题意: 在链表类中实现这些功能: get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。 addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。 addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。 addAtIndex(index,val):在链表中的第 index 个节点之前添加
代码随想录
2020/08/18
3430
707. 设计链表
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。 在链表类中实现这些功能: get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。 addAtHead(val):在链表的第一个元素之前添加一个值为 val
CaesarChang张旭
2022/04/13
2620
【day10】LeetCode(力扣)刷题(注释详细)[707.设计链表][278.第一个错误的版本][98. 验证二叉搜索树]
解题思路: 使用二分查找来寻找第一个错误版本; 首先确定左右边界; 利用左右边界确认中间版本,判断是否为错误版本: 若不是错误版本:第一个错误版本就在【mid+1,right】之间 如果是错误版本:第一个错误版本就在【left,mid】之间
.29.
2022/11/15
2690
【day10】LeetCode(力扣)刷题(注释详细)[707.设计链表][278.第一个错误的版本][98. 验证二叉搜索树]
【leetcode速通java版】03——移除链表元素,设计链表,反转链表
解法: 还挺简单的,为了对第一个数据归一化操作,定义头指针,不含数据的虚拟头节点。
半旧518
2022/10/26
2060
【leetcode速通java版】03——移除链表元素,设计链表,反转链表
代码随想录day02--链表
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
ma布
2024/12/25
590
代码随想录day02--链表
图解LeetCode——707. 设计链表(难度:中等)
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
爪哇缪斯
2023/05/10
1860
图解LeetCode——707. 设计链表(难度:中等)
什么是链表?
链表是数据结构之一,其中的数据呈线性排列。在链表中,数据的添加和删除都较为方便,就是访问比较耗费时间。
武培轩
2020/02/15
6790
2022-12-01
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
云边小卖部
2022/12/02
3200
2022-12-01
LeetCode通关:听说链表是门槛,这就抬脚跨门而入
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的引用。也可以称之为数据域和指针域。
三分恶
2021/07/27
4380
2023年前端面试题汇总-数据结构(链表)
在计算机里,不保存在连续存储空间中,而每一个元素里都保存了到下一个元素的地址的数据结构,我们称之为链表(Linked List)。链表上的每一个元素又可以称它为节点(Node),而链表中第一个元素,称它为头节点(Head Node),最后一个元素称它为尾节点(Tail Node)。
越陌度阡
2023/06/10
1.1K0
2023年前端面试题汇总-数据结构(链表)
链表常见操作总结及C++实现
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字
evenleo
2020/08/21
5270
LeetCode 146. LRU缓存机制(哈希链表)
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
Michael阿明
2021/02/20
5160
LeetCode 146. LRU缓存机制(哈希链表)
推荐阅读
相关推荐
LeetCode 707. 设计链表(List)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文