第二章_链表_part01

移除链表元素

题目描述

题意:删除链表中等于给定值 val 的所有节点。

示例 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 输出:[]

解题思路

根据链表的特性,如果需要删除链表中的某个元素的话,可以将当前节点的上一个节点指向当前节点的下一个节点,然后释放当前节点即可。可以采用两种方式进行链表元素移出的目的:

  • 直接使用原来的链表进行删除操作

    • 使用一个循环来删除链表头部可能出现的一个或多个目标值节点,然后使用另一个循环来遍历链表的其余部分,删除遇到的每一个目标值节点。
  • 设置一个虚拟头结点进行删除操作

    • 设定一个虚拟头结点指向头结点,然后利用链表的特性依次遍历链表,删除指定节点,好处就是无需进行头节点的判断,删除操作均采用同一个逻辑。

注意:C++中需要手动释放节点,否则会影响内存空间占用率

代码实现

测试地址:https://leetcode.cn/problems/remove-linked-list-elements/description/

直接使用原来的链表:

class Solution {
public:
    // 函数,用于移除链表中所有值为val的节点
    ListNode* removeElements(ListNode* head, int val) {
        // 循环删除头部是目标值val的节点
        while (head != nullptr && head->val == val) {
            ListNode* tmp = head; // 临时保存当前要删除的节点
            head = head->next; // 移动头指针到下一个节点
            delete tmp; // 释放被删除的节点的内存
        }
        // cur用来遍历链表,从新的头节点开始
        ListNode* cur = head;
        // 遍历链表直到cur为nullptr(链表尾部)
        while (cur != nullptr && cur->next != nullptr) {
            // 如果当前cur的下一个节点的值等于val,那么这个节点需要被删除
            if (cur->next->val == val) {
                ListNode* tmp = cur->next; // 临时保存当前要删除的节点
                cur->next = cur->next->next; // 跳过当前要删除的节点,将cur的next指向当前要删除的节点的下一个节点
                delete tmp; // 释放被删除的节点的内存
            } else {
                // 如果当前cur的下一个节点的值不是val,则移动cur到下一个节点
                cur = cur->next;
            }
        }
        // 返回处理后的链表头节点
        return head;
    }
};

设置虚拟头结点:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 创建一个哑节点,也称为伪头节点
        ListNode* dummyHead = new ListNode();
        // 将哑节点的下一个节点指向原始链表的头节点
        dummyHead->next = head;
    
        // 使用cur指针遍历链表
        ListNode* cur = dummyHead;
        while (cur->next != nullptr) {
            // 如果当前cur指向的节点的下一个节点的值等于目标值val
            if (cur->next->val == val) {
                // 临时保存需要删除的节点
                ListNode* tmp = cur->next;
                // 跳过需要删除的节点,即从链表中移除
                cur->next = cur->next->next;
                // 释放(删除)需要删除的节点
                delete tmp;
            } else {
                // 否则,移动cur指针到下一个节点
                cur = cur->next;
            }
        }
        // 更新头节点指向,因为原始头节点可能被删除
        head = dummyHead->next;
        // 删除哑节点
        delete dummyHead;
        // 返回可能已更新的头节点
        return head;
    }
};

设计链表

题目描述

题意:

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

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

解题思路

总体思路

  • 使用结构体 LinkNode 定义链表节点,每个节点保存一个整数值和一个指向下一个节点的指针。
  • 使用类 MyLinkedList 封装链表操作,其中包括一个私有变量 _dummyHead 作为哑节以及一个 _size 存储链表节点数。
  • 在进行插入和删除操作时,首先检查索引的有效性,然后遍历到目标位置,执行相应的节点插入或删除操作。
  • 在添加、删除节点或者获取节点值的函数中,都有对索引和链表边界的检查,确保操作的有效性。

时空复杂度分析:

  • 时间复杂度: 涉及 index 的相关操作为 O(index), 其余为 O(1)
  • 空间复杂度: O(n)

代码实现

测试地址:https://leetcode.cn/problems/design-linked-list/

class MyLinkedList {
public:
    // 定义链表节点结构
    struct LinkNode {
        int val; // 节点的值
        LinkNode* next; // 指向下一个节点的指针
        // 构造函数,初始化节点的值为val,下一个节点为nullptr
        LinkNode(int val) : val(val), next(nullptr) {}
    };

    // 构造函数,初始化链表
    MyLinkedList() {
        _dummyHead = new LinkNode(0); // 创建哑节点
        _size = 0; // 初始化链表大小为0
    }

    // 获取指定索引的节点的值
    int get(int index) {
        if (index < 0 || index > (_size - 1)) { // 检查索引是否有效
            return -1; // 返回-1表示索引无效
        }
        LinkNode* cur = _dummyHead->next; // 从第一个实际节点开始
        while (index--) { // 移动到指定索引的节点
            cur = cur->next;
        }
        return cur->val; // 返回节点的值
    }

    // 在链表头部添加新节点
    void addAtHead(int val) {
        LinkNode* newNode = new LinkNode(val); // 创建新节点
        newNode->next = _dummyHead->next; // 新节点的下一个节点指向当前头节点
        _dummyHead->next = newNode; // 更新哑节点的next指向新节点
        _size++; // 更新链表大小
    }

    // 在链表尾部添加新节点
    void addAtTail(int val) {
        LinkNode* newNode = new LinkNode(val); // 创建新节点
        LinkNode* cur = _dummyHead; // 从哑节点开始
        while (cur->next != nullptr) { // 移动到链表的最后一个节点
            cur = cur->next;
        }
        cur->next = newNode; // 最后一个节点的next指向新节点
        _size++; // 更新链表大小
    }

    // 在指定索引处添加新节点
    void addAtIndex(int index, int val) {
        if (index > _size) // 如果索引大于链表大小,则不添加
            return;
        if (index < 0) // 如果索引小于0,则添加到头部
            index = 0;
        LinkNode* newNode = new LinkNode(val); // 创建新节点
        LinkNode* cur = _dummyHead; // 从哑节点开始
        while (index--) { // 移动到要插入位置的前一个节点
            cur = cur->next;
        }
        newNode->next = cur->next; // 新节点的next指向当前节点的next
        cur->next = newNode; // 当前节点的next指向新节点
        _size++; // 更新链表大小
    }

    // 删除指定索引的节点
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) { // 检查索引是否有效
            return; // 如果无效则不执行删除操作
        }
        LinkNode* cur = _dummyHead; // 从哑节点开始
        while (index--) { // 移动到要删除节点的前一个节点
            cur = cur->next;
        }
        LinkNode* tmp = cur->next; // 临时保存要删除的节点
        cur->next = cur->next->next; // 更新前一个节点的next指向
        delete tmp; // 删除节点
        tmp = nullptr; // 避免野指针
        _size--; // 更新链表大小
    }

    // 打印链表内容
    void printLinkedList() {
        LinkNode* cur = _dummyHead; // 从哑节点开始
        while (cur->next != nullptr) { // 遍历链表直到最后一个节点
            cout << cur->next->val << " "; // 打印节点的值
            cur = cur->next; // 移动到下一个节点
        }
        cout << endl; // 打印换行符
    }

private:
    int _size; // 链表大小
    LinkNode* _dummyHead; // 哑节点,用于简化链表操作
};

翻转链表

题目描述

题意:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

解题思路

双指针思路

总体思路:

  • 定义一个cur指针,指向头结点,然后再定义一个pre指针,初始化为null
  • 因为头结点经过翻转后会变成尾节点,指向空节点,也就是pre指针对应的节点位置,因此需要通过tmp节点保存原先节点指向的下一个节点
  • 接下来就是翻转操作了,将cur->next指向pre节点,分别移动pre和cur节点到下一个节点维持
  • 循环上述操作,最后cur指针会指向空节点,此时循环结束,此时pre指向的位置就是头结点的位置,返回pre节点即可。

时空复杂度分析:

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

递归思路

总体思路:

  • 根据双指针法确定递归终止条件:当cur执行的元素为nullptr时,返回pre节点指向的元素
  • 初始化双指针,将cur指向头结点,pre指向nullptr节点
  • 递归赋值流程:使用tmp保存cur指向的下一个元素,将cur指向的下一个元素修改为pre,开始递归调用,由于此时pre是由cur决定的,cur由tmp决定的,因此递归参数传入的是cur和tmp对应的节点。

时空复杂度:

  • 时间复杂度: O(n), 要递归处理链表的每个节点
  • 空间复杂度: O(n), 递归调用了 n 层栈空间

代码实现

测试地址:https://leetcode.cn/problems/reverse-linked-list/description/

双指针代码实现:

/**
 * 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;
        ListNode* pre = nullptr;
        while (cur) {
            tmp = cur->next; // 通过临时指针保存cur的下一个节点
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur 指针
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

递归代码实现:

/**
 * 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* cur = head;
        // ListNode* pre = NULL;
        return reverse(nullptr, head);
    }
    ListNode* reverse(ListNode* pre, ListNode* cur) {
        if (cur == nullptr)
            return pre;
        ListNode* tmp = cur->next;
        cur->next = pre;
        // 对应双指针法的代码
        // pre = cur
        // cur = tmp
        return reverse(cur, tmp);
    }
};
  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
链表归并是指将两个有序链表合并成一个有序链表的过程。实验11_9的目的是通过链表归并的实现,加深对链表操作的理解和应用。 具体实现步骤如下: 1. 定义一个链表结构体Node,包含一个整型数据域和一个指向下一个节点的指针域。 2. 定义一个链表归并函数merge,输入参数为两个链表的头节点指针head1和head2,输出参数为归并后的链表头节点指针head3。 3. 在merge函数内部,定义一个临时节点指针p,将其指向头节点较小的那个链表的头节点,将头节点较大的那个链表的头节点赋值给head3。 4. 在while循环中,判断p的指针域是否为空,如果为空,则将p的指针域指向头节点较大的那个链表的头节点,并将该链表的头节点指针head3赋值给p。 5. 在while循环中,判断p所指向的节点的数据域与另一个链表头节点的数据域的大小关系,将p的指针域指向数据域较小的节点。 6. 在while循环结束后,将剩余的节点直接接到归并后的链表尾部。 完整代码如下: ``` #include<iostream> using namespace std; struct Node{ int data; Node* next; }; Node* merge(Node* head1,Node* head2){ if(head1==NULL) return head2; if(head2==NULL) return head1; Node* head3=NULL; Node* p=NULL; if(head1->data<head2->data){ p=head1; head1=head1->next; } else{ p=head2; head2=head2->next; } head3=p; while(head1!=NULL&&head2!=NULL){ if(head1->data<head2->data){ p->next=head1; head1=head1->next; } else{ p->next=head2; head2=head2->next; } p=p->next; } if(head1!=NULL) p->next=head1; if(head2!=NULL) p->next=head2; return head3; } int main(){ Node* head1=new Node; Node* head2=new Node; Node* p1=head1; Node* p2=head2; int n,m; cout<<"请输入第一个链表的长度:"; cin>>n; cout<<"请输入第一个链表的元素:"; for(int i=0;i<n;i++){ int x; cin>>x; Node* node=new Node; node->data=x; p1->next=node; p1=node; } cout<<"请输入第二个链表的长度:"; cin>>m; cout<<"请输入第二个链表的元素:"; for(int i=0;i<m;i++){ int x; cin>>x; Node* node=new Node; node->data=x; p2->next=node; p2=node; } Node* head3=merge(head1->next,head2->next); cout<<"合并后的链表为:"; while(head3!=NULL){ cout<<head3->data<<" "; head3=head3->next; } cout<<endl; return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值