Day3 链表01

Day3 链表01

一、链表基础

  • 什么是链表?

    • 链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点指针域指向null空指针的意思)。
    • 链接的入口节点称为链表的头结点也就是head
    • 单链表、双链表、循环链表
  • 链表的存储方式

    • 链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
  • 链表的定义

// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

二、移除链表元素

203. 移除链表元素 - 力扣(LeetCode)

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

示例 1:

img

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

1、原链表删除元素

对于在原链表中删除元素,要注意头节点以及非头节点的区别

  • 删除的如果是头节点,首先头节点不能为空;其次,需要用到while而不是if,因为可能出现数组为[1, 1, 1, 1],需要删除的元素val=1的情况。这种情况下,一直删除的都是头节点,所以删除头节点是一个持续的过程;

  • 删除的如果是非头节点,则直接用到如下图思路
    在这里插入图片描述

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 删除头结点
        while (head != NULL && head->val == val) { // 注意这里不是if
            //注意自己释放创建的指针
            ListNode* tmp = head;
            head = head->next;
            delete tmp;
        }

        // 删除非头结点
        ListNode* cur = head;
        while (cur != NULL && cur->next!= NULL) {
            if (cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        return head;
    }
};

2、虚拟头节点

如果我们想节约代码,将头节点以及非头节点归为一类,只需要设置一个虚拟头节点dummyHead,这样我们原来的头节点也变为了普通节点,不需要单独考虑;

注意ListNode* cur = dummyHead,这个地方我们要设置 cur = dummyHead,因为如果cur = head,那么操作head的时候,他就又不是一个普通的节点了,类似于法一里面的ListNode* cur = head,是一样的道理。
在这里插入图片描述

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
        dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
        ListNode* cur = dummyHead;
        
        while (cur->next != NULL) {
            if(cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        return dummyHead->next;
    }
};

三、设计链表

707. 设计链表 - 力扣(LeetCode)

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

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

五个函数的思路:

  • 获取链表第index个节点的值:循环遍历;
  • 在头节点前添加节点:即在虚拟头节点和头节点之间添加节点;
  • 在尾部添加节点:遍历,当cur指向尾节点之后,通过cur->next添加节点;
  • 任意添加节点:
    • index<0,则添加到为头节点;
    • index=length,添加到尾部后;
    • index>length,return;
    • 即0<=index<=length时,通过遍历cur到index节点的前一个节点,来添加;
  • 删除任意节点:循环遍历。
class MyLinkedList {
public:
    //定义链表结构体
    struct LinkNode{
        int val;
        LinkNode* next;
        LinkNode(int val):val(val), next(nullptr){}
    };

    MyLinkedList() {
        dummyNode = new LinkNode(0);
        length = 0;
    }
    
    int get(int index) {
        LinkNode* cur = dummyNode;
        if(index > (length - 1) || index < 0) return -1;
        else{
            for(int i = 0; i <= index; i++){ // 这里将循环次数改为 <= index
                cur = cur->next;
            }                        
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkNode* add = new LinkNode(val);
        LinkNode* cur = dummyNode->next;
        dummyNode->next = add;
        add->next = cur;
        length++;
    }
    
    void addAtTail(int val) {
        LinkNode* add = new LinkNode(val);
        LinkNode* cur = dummyNode;
        while(cur->next != nullptr)
        {
            cur = cur->next;
        }
        cur->next = add;
        length++;
    }
    
    void addAtIndex(int index, int val) {
        if(index > length) return;
        if(index < 0) index = 0;
        LinkNode* add = new LinkNode(val);
        LinkNode* cur = dummyNode;

        while(index--){
            cur = cur->next;
        } 

        LinkNode*temp = cur->next;
        cur->next = add;
        add->next = temp;
        length++;
    }
    
    void deleteAtIndex(int index) {
        if(index < 0 || index >= length) return; // 添加对 index 有效性的检查
        LinkNode* cur = dummyNode;
        for(int i = 0; i < index; i++)
        {
            cur = cur->next;
        }
        if (cur->next != nullptr) {
            cur->next = cur->next->next;
            length--;
        }
    }
private:
    LinkNode* dummyNode;
    int length;
};

四、反转链表

1、双指针

  • 首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
  • 开始反转,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
  • 先移动pre节点到cur节点的位置。
  • 再移动cur节点,指向tmp节点。
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

2、递归

  • return reverse(cur,temp);

    • 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
    • pre = cur;
    • cur = temp;
  • return reverse(NULL, head);

    • 和双指针法初始化是一样的逻辑
    • ListNode* cur = head;
    • ListNode* pre = NULL;
class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
        if(cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
        // pre = cur;
        // cur = temp;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
        // 和双指针法初始化是一样的逻辑
        // ListNode* cur = head;
        // ListNode* pre = NULL;
        return reverse(NULL, head);
    }
};

五、总结

今天的题目有些其实之前做过,但是今天再做感觉就是脑子会了,手不会,链表有些基础知识也有些忘了;递归还不太理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值