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

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

203.移除链表元素

题目链接:https://leetcode.cn/problems/remove-linked-list-elements/description/

1.直接法

因为之前在王道网课上系统地打过一遍基础,一开始还是很自信的;然后一做题…只能说考研题和力扣题还是有差距的。

先展示一下代码:

ListNode* removeElements(ListNode* head, int val) {
        while(head!=NULL&&head->val ==val)
        {
            ListNode*q =head;
            head = head->next;
            delete q;
        }
        ListNode*p=head;
        while(p!=NULL&&p->next!=NULL)
        {
            if(p->next->val==val)
            {
            ListNode*q =p->next;
            p->next = p->next->next;
            delete q;
            }
            else
            p=p->next;
        }
        return head;
    }

主要有3个注意点:

  1. 头结点内是有数据的(这和王道讲的不一样),一旦头结点内的数据满足删除要求,就会像多米诺骨牌一样,一个一个往后删,所以需要用while循环

  2. 使用C++写该代码时,需要及时清理内存释放被删除的节点,具体方法是:

			   ListNode*q = Deleted;
			  	delete q;
Deleted指的是被删除的结点。
  1. 删除头结点逻辑和删除非头结点的逻辑时不一样的

    另外,不推荐大家用双指针来做删除的逻辑,容易乱且容易错(本人的血泪教训哈哈)

2.虚拟头结点

为了让删除头结点和删除非头结点是相同的逻辑,我们可以在原来的头结点前面再加上一个结点,也就是虚拟头结点,具体代码如下:

ListNode* removeElements(ListNode* head, int val) {
            ListNode*dummyhead = new ListNode(0);
            dummyhead->next = head;
            ListNode*p=dummyhead;
            while(p->next!=NULL)
            {
                if(p->next->val==val)
                {
                    p->next=p->next->next;
                }
                else
                p=p->next;
            }
            return dummyhead->next;
        }

虚拟头结点真的是方便又巧妙,唯一 需要注意的是返回值不是dummyhead(这只是虚拟头结点)或者head(因为head结点可能被删除了),而是dummyhead->next;

707.设计链表

题目链接:https://leetcode.cn/problems/design-linked-list/description/
这题就是链表的基本操作,代码比较长。我原来写的没有用虚拟头结点,下面展示的是卡哥用虚拟头结点的代码,有明显的简化:

class MyLinkedList {
public:
    // 定义链表节点结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    };

    // 初始化链表
    MyLinkedList() {
        _dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
        _size = 0;
    }

    // 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
    int get(int index) {
        if (index > (_size - 1) || index < 0) {
            return -1;
        }
        LinkedNode* cur = _dummyHead->next;
        while(index--){ // 如果--index 就会陷入死循环
            cur = cur->next;
        }
        return cur->val;
    }

    // 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = _dummyHead->next;
        _dummyHead->next = newNode;
        _size++;
    }

    // 在链表最后面添加一个节点
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode;
        _size++;
    }

    // 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果index大于链表的长度,则返回空
    // 如果index小于0,则在头部插入节点
    void addAtIndex(int index, int val) {

        if(index > _size) return;
        if(index < 0) index = 0;        
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        _size++;
    }

    // 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
    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;
        //delete命令指示释放了tmp指针原本所指的那部分内存,
        //被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
        //如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
        //如果之后的程序不小心使用了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;

};

206.反转链表

题目链接:https://leetcode.cn/problems/reverse-linked-list/

1.双指针法

凭借之前王道网课模糊的印象,反转链表的方法大概有头插法、双指针法。首先我考虑了用双指针法,然后没有下文了…在看完了卡哥的讲解之后,我才明白还需要在来一个指针(好家伙,三指针),具体代码如下:

ListNode* reverseList(ListNode* head) {
        ListNode*pre=NULL;
        ListNode*cur=head;
        ListNode*temp;
        while(cur!=NULL)
        {
            temp = cur->next;
            cur->next=pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }

分别设置了3个指针,作用各不相同而且都不可或缺(我替大家试过了,缺一个绝对做不出来…)

首先是cur指针,它就是指向我们现在要研究的结点,属于正常操作。

然后是pre指针,要知道,当我们将链表反转时,是需要将当前结点的next指向前一个结点的。那么问题来了,怎么获取前一个结点?答案:提前设置一个pre指针指向cur的前一个结点就行了。

最后是temp指针,跟上面思路相似。当我们把cur->next指向pre的时候,问题来了:cur的下一个结点你怎么表示它?这链表已经断了呀!不急(其实当时我急得很),用temp指针指向cur原来的下一个结点就行了。

其实我们发现,这3个指针很难说我们一下子就能想出来,都是我们思路遇到了问题然后为了解决它而想出来的,所以理解这道题的思考和逻辑过程可能比这个方法本身更重要。

2.递归法

逻辑与双指针一样,感觉就是炫技…

具体代码如下(来自卡哥,我写不出来…):

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);
    }
  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值