代码随想录算法训练营第三天|Leetcode203 移除链表元素、Leetcode707 设计链表、Leetcode206 反转链表

● 链表

对于数组而言存在一个最大的缺点,就是我们的插入和删除时需要移动大量的元素,需要消耗大量的时间,同时必须为数组开足够的空间,否则有溢出风险。
而链表通过不连续的储存方式,自适应内存大小,以及指针的灵活使用,巧妙的简化了上述的内容。链表利用结构体的设置,额外开辟出一份内存空间去作指针,它总是指向下一个结点,一个个结点通过NEXT指针相互串联,就形成了链表。
链表大致包括包含单链表,双链表,循环单链表,实际应用中的功能不同,但实现方式都差不多。

单链表

单链表是链表中最简单的一种形式,每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。单链表的插入和删除操作方便,但是查找效率低。
单链表

双向链表

双向链表中不仅有指向后一个节点的指针,还有指向前一个节点的指针。第一个节点的"前连接"指向NULL,最后一个节点的"后连接"指向NULL。这样可以从任何一个节点访问前一个节点,也可以访问后一个节点,以至整个链表。一般是在需要大批量的另外储存数据在链表中的位置的时候用。
双向链表

循环链表

循环链表在一个循环链表中, 首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,你开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点。循环链表可以被视为"无头无尾"。 循环链表的无边界使得在这样的链表上设计算法会比普通链表更加容易。对于新加入的节点应该是在第一个节点之前还是最后一个节点之后可以根据实际要求灵活处理。
循环链表

● Leetcode203 移除链表元素

题目链接:Leetcode203 移除链表元素
视频讲解:代码随想录|移除链表元素|删除链表结点
题目描述:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例 1:
示例1
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]

● 解题思路

思路:
方法一: 当删除结点为链表中间某一结点时,使用cur->next不断寻找,当cur->next->val == val时,我们可以直接将cur->next->next赋值给cur->next,最后删除目标节点即可。如图所示:
删除链表中间结点
但当head的值需要被删除时,因为其没有前驱结点,所以无法像删除中间某个结点一样,因此我们直接将head->next赋值给head即可。如图所示:
删除头结点
因此需要我们对出现的两种情况都进行操作,代码就会稍微多一些。

时间复杂度:O(n) 空间复杂度:O(1)

方法二: 方法一中需要对两种情况都要进行代码编写,有没有一种方法适用于两种情况,从而减少代码量?
当我们构造一个新的dummynode作为头结点的前驱结点时,就可以将删除头结点和中间结点两个情况进行结合,这样即使头结点是我们要删除的结点,也可以像删除中间结点一样使用同一套方法。

时间复杂度:O(n) 空间复杂度:O(1)

对于时间复杂度而言,因为两种方法都遍历了一遍链表,所以时间复杂度都是O(n)

● 代码实现

方法一

//方法一
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        while(head && head->val == val)
        {
            ListNode* tmp = head;
            head = head->next;
            delete tmp;
        }
        ListNode* cur = head;
        while(cur && cur->next)
        {
            if(cur->next->val == val)
            {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            }
            else
            {
                cur = cur->next;
            }
        }
        return head;
    }
};

方法二

//方法二
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummynode = new ListNode(0);
        dummynode->next = head;
        ListNode* cur = dummynode;
        while(cur->next)
        {
            if(cur->next->val == val)
            {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            }
            else
            {
                cur = cur->next;
            }
        }
        head = dummynode->next;
        delete dummynode;
        return head;
    }
};

● Leetcode707 设计链表

题目链接:Leetcode707 设计链表
视频讲解:代码随想录|设计链表|链表基本操作
题目描述:你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList 类:
MyLinkedList() 初始化 MyLinkedList 对象。
int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。
示例:
输入
[“MyLinkedList”, “addAtHead”, “addAtTail”, “addAtIndex”, “get”, “deleteAtIndex”, “get”]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
myLinkedList.get(1); // 返回 3

● 代码实现

单链表

class MyLinkedList {
public:
    struct LinkedNode
    {
        int val; // 值域
        LinkedNode* next; // 指针域
        LinkedNode(int val) : val(val), next(nullptr){}
    };

    //初始化 MyLinkedList 对象, 对MyLinkedList的属性(_dummyhead和_size)进行初始化
    MyLinkedList() {
        _dummyhead = new LinkedNode(0);
        _size = 0;
    }

    //获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1
    int get(int index) {
        if(index < 0 || index > (_size - 1)) return -1;
        LinkedNode* cur = _dummyhead->next;
        while(index--)
        {
            cur = cur->next;
        }
        return cur->val;
    }
    
    //将一个值为 val 的节点插入到链表中第一个元素之前
    void addAtHead(int val) {
        LinkedNode* newnode = new LinkedNode(val);
        newnode->next = _dummyhead->next;
        _dummyhead->next = newnode;
        _size++;
    }
    
    //将一个值为 val 的节点追加到链表中作为链表的最后一个元素
    void addAtTail(int val) {
        LinkedNode* cur = _dummyhead;
        while(cur->next != nullptr)
        {
            cur = cur->next;
        }
        LinkedNode* newnode = new LinkedNode(val);
        cur->next = newnode;
        _size++;
    }
    
    //将一个值为 val 的节点插入到链表中下标为 index 的节点之前
    void addAtIndex(int index, int val) {
        if(index > _size) return;
        if(index < 0) index = 0;
        LinkedNode* cur = _dummyhead;
        while(index--)
        {
            cur = cur->next;
        }
        LinkedNode* newnode = new LinkedNode(val);
        newnode->next = cur->next;
        cur->next = newnode;
        _size++;
    }
    
    //如果下标有效,则删除链表中下标为 index 的节点
    void deleteAtIndex(int index) {
        if(index >= _size || index < 0) return;
        LinkedNode* cur = _dummyhead;
        while(index--)
        {
            cur = cur->next;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        tmp = nullptr;
        _size--;
    }

    //打印链表
    void printLinkedList()
    {
        LinkedNode* cur = _dummyhead;
        while(cur->next != nullptr)
        {
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout<<endl;
    }
private:
    int _size; // 链表长度
    LinkedNode* _dummyhead; // 虚拟头结点
};

/**
 * 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);
 */

双链表

class MyLinkedList {
public:
    struct DoubleLinkedNode
    {
        int val;
        DoubleLinkedNode *pre, *next;
        DoubleLinkedNode(int val) : val(val), pre(nullptr), next(nullptr){}
    };

    MyLinkedList() {
        this->_size = 0;
        this->head = new DoubleLinkedNode(0);
        this->tail = new DoubleLinkedNode(0);
        head->next = tail;
        tail->pre = head;
    }
    
    int get(int index) {
        if(index < 0 || index > (_size - 1)) return -1;
        DoubleLinkedNode *cur;
        if(index + 1 < _size - index)
        //如果 index + 1 小于 size - index,意味着从头节点开始遍历的步数更少。在这种情况下,使用 curr = head 将当前节点指针初始化为头节点,并通过 for 循环移动 curr 节点指针,直到达到目标索引位置
        {
            cur = head;
            for(int i = 0; i <= index; i++)
            {
                cur = cur->next;
            }
        }
        else
        //如果 index + 1 大于或等于 size - index,意味着从尾节点开始遍历的步数更少。在这种情况下,使用 curr = tail 将当前节点指针初始化为尾节点,并通过 for 循环反向移动 curr 节点指针,直到达到目标索引位置
        {
            cur = tail;
            for(int i = 0; i < _size - index; i++)
            {
                cur = cur->pre;
            }
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        addAtIndex(0, val);
    }
    
    void addAtTail(int val) {
        addAtIndex(_size, val);
    }
    
    void addAtIndex(int index, int val) {
        if(index > _size) return;
        index = max(0, index);
        //确保索引 index 不会小于 0。这是为了处理一种特殊情况,即当 index 的值为负数时,将其修正为 0。如果 index 是负数,表示要在链表头部之前插入节点。通过将 index 的值修正为 0,可以确保新节点将成为链表的新头节点。
        DoubleLinkedNode *pre, *suc;
        if(index < _size - index)
        {
            pre = head;
            for(int i = 0; i < index; i++)
            {
                pre = pre->next;
            }
            suc = pre->next;
        }
        else
        {
            suc = tail;
            for(int i = 0; i < _size - index; i++)
            {
                suc = suc->pre;
            }
            pre = suc->pre;
        }
        _size++;
        DoubleLinkedNode *newnode = new DoubleLinkedNode(val);
        newnode->pre = pre;
        newnode->next = suc;
        pre->next = newnode;
        suc->pre = newnode;
    }
    
    void deleteAtIndex(int index) {
        if(index < 0 || index > (_size - 1)) return;
        DoubleLinkedNode *pre, *suc;
        if(index < _size - index)
        {
            pre = head;
            for(int i = 0; i < index; i++)
            {
                pre = pre->next;
            }
            suc = pre->next->next;
        }
        else
        {
            suc = tail;
            for(int i = 0; i < _size - index - 1; i++)
            {
                suc = suc->pre;
            }
            pre = suc->pre->pre;
        }
        _size--;
        DoubleLinkedNode *tmp = pre->next;
        pre->next = suc;
        suc->pre = pre;
        delete tmp;
    }
private:
    int _size; //链表长度
    DoubleLinkedNode *head;
    DoubleLinkedNode *tail;
};

● Leetcode707 设计链表

题目链接:Leetcode707 设计链表
视频讲解:代码随想录|设计链表|链表基本操作
题目描述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
示例1
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
示例2
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]

● 解题思路

思路:
新创建一个nullptr作为返回链表的末尾,然后依次将每一个结点挂到新链表的头结点。对于实现而言,也正是将每一个结点的next反转的过程。
但如果我们只有precur的时候,会发现当我们完成第一个节点的反转之后就会丢失了对之后结点的访问,因此还需要tmp帮助我们在反转之前做存放后续结点的工作。如图所示:
反转链表

● 代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* tmp;
        ListNode* cur = head, *pre = nullptr;
        while(cur)
        {
            tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};
  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值