代码随想录算法训练营Day3 | 203.移除链表元素 707.设计链表 206.反转链表

Day3

链表理论基础

  • 链表是通过指针连接的线性结构,链表的节点在内存中不连续存在(不像数组)
  • 链表的每一个节点由两部分组成:数据域+指针域
    • 指针域指向下一个节点,最后一个节点的指针指向nullptr(循环链表除外)

链表的类型

单链表

  • 单链表的节点由两部分组成:数据+后向指针
    • 后向指针指向链表的下一个节点
  • 由于单链表只有后向指针,只能按一个方向访问元素
    单链表示意图

双链表

  • 单链表的节点由三部分组成:数据+前向指针+后向指针
    • 前向和后向指针分别指向当前节点的上一个和下一个节点
  • 由于双链表有两个方向的指针,可以随意前后访问元素
    双链表示意图

循环链表

  • 循环链表只是连接方式上有所不同,既可以是单链表也可以是双链表
  • 循环链表中最后一个节点的后向指针指向的是头节点(形成一个环)
    • 如果该链表是双链表,那么头指针的前向指针指向最后一个元素

链表的存储方式与定义

  • 链表的节点在内存中不连续存在。(不像数组在内存中是连续分布)

  • 常见链表的定义如下

    template<typename T>
    struct ListNode
    {
        ListNode* prev;    // 前向指针
        ListNode* next;    // 后向指针
        T         data;    // 数据域
        
        ListNode(T x) : prev(nullptr), next(nullptr), data(x) { 
    }
    

链表的操作

插入节点

  • 以单链表为例,要在如下位置插入一个节点F
    在这里插入图片描述

    • 首先遍历到C的位置,随后修改F的next指针指向D,然后修改C的next指针指向F,完成链表的插入。
      • 不使用额外空间,先修改C行不行: 不行,由于链表靠指针连接在一起,先修改C的next指向F后,我们失去了唯一访问节点D的入口,无法在后续让F的next指针指向D。
      • 就想先改C,可以用额外空间: 在遍历到C的位置时,使用一个 额外指针 指向D。随后修改C的next指向F。由于先前用一个指针指向D,此时可以修改F的next指向D完成插入。

删除节点

  • 以单链表为例,要删除节点D
    在这里插入图片描述

    • 要删除,只要将C节点的next指针指向E就可以了。
    • 注意此时D仍然存在于内存,只是修改next的这个行为把D从链表中“排除”了,但没有删除
      • 如果是C/C++,这里就需要手动delete掉这个节点,避免内存泄漏
      • 其他语言有垃圾回收,不用手动释放了

链表时间开销

插入开销

  • 插入分为两部分:查找+插入
  • 查找的时间是O(n)
    • 因为链表的分布特性,不支持随机访问,只能从前向后遍历到我们要插入的位置。最坏情况下,要插入到最后一个节点,我们要遍历前面的所有n-1个元素。
  • 插入的时间是O(1)
    • 只需要修改节点的next指针就可以完成插入

删除开销

  • 删除和查找一样,也分为两部分:查找+插入
  • 查找的时间是O(n)
    • 从前向后遍历到我们需要删除的节点。最坏情况下删除最后一个节点,要遍历前面的所有n-1个元素。
  • 删除的时间是O(1)
    • 只需要修改元素的next指针就可以完成删除

和数组相比

在这里插入图片描述


203. Remove Linked List Elements

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if(head == nullptr)
            return head;
        ListNode* fakeHead = new ListNode(-1);
        fakeHead->next = head;
        ListNode* curr = fakeHead;
        while(curr->next != nullptr)
        {
            if(curr->next->val == val)
            {
                ListNode* toDelete = curr->next;
                curr->next = toDelete->next;
                delete toDelete;
            }
            else
                curr = curr->next;
        }
        head = fakeHead->next;
        delete fakeHead;
        return head;
    }
};

707. Design Linked List

class MyLinkedList {
public:
    struct ListNode {
        int val;
        ListNode* next;

        ListNode();

        ListNode(int _val, ListNode* _next) {
            val = _val;
            next = _next;
        }
        
        ListNode(int _val) {
            val = _val;
            next = nullptr;
        }
    };

    ListNode* fakeHead;
    int length;

    MyLinkedList() {
        fakeHead = new ListNode(-1);
        length = 0;
    }

    int get(int index) {
        if (index < 0 || index >= length)
            return -1;
        ListNode* curr = fakeHead;
        while (index > 0) {
            curr = curr->next;
            --index;
        }
        return curr->next->val;;
    }

    void addAtHead(int val) {
        ListNode* newNode = new ListNode(val, fakeHead->next);
        fakeHead->next = newNode;
        ++length;
    }

    void addAtTail(int val) {
        ListNode* curr = fakeHead;
        while(curr->next != nullptr) {
            curr = curr->next;
        }
        ListNode* newNode = new ListNode(val);
        curr->next = newNode;
        ++length;
    }

    void addAtIndex(int index, int val) {
        if (index == 0) {
            addAtHead(val);
            return;
        } else if (index == length) {
            addAtTail(val);
            return;
        } else if (index > length)
            return;
        ListNode* curr = fakeHead;
        while (index > 0) {
            curr = curr->next;
            --index;
        }
        ListNode* newNode = new ListNode(val, curr->next);
        curr->next = newNode;
        ++length;
    }

    void deleteAtIndex(int index) {
        if(index >= length || index < 0)
            return;
        ListNode* curr = fakeHead;
        while (index > 0) {
            curr = curr->next;
            --index;
        }
        ListNode* tmp = curr->next;
        curr->next = curr->next->next;
        delete tmp;
        tmp = nullptr;
        --length;
    }
};

206. Reverse Linked List

  • 反转链表,一道比较经典的题目,主要是要处理好指针间的关系
  • 我们利用三个指针反转链表,通过名字能直观判断出指向的都是谁,分别是prev, curr, next
  • 虽然用了三个指针,但注意在循环的时候,我们每次只反转prev和curr,next主要起到一个跟踪的作用。要是把next也反转了,就丢了对后续节点的访问入口了
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == nullptr || head->next == nullptr)
            return head;
        ListNode* prev = nullptr;
        ListNode* curr = head;
        ListNode* next = head->next;

        while(curr->next)
        {
            curr->next = prev;
            prev = curr;
            curr = next;
            next = next->next;
        }
        curr->next = prev;
        head = curr;
        return head;
    }
};
  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值