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

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

这里是一看就会,一学就费同学的博客,老规矩,大部分代码来源于y总

203.移除链表元素

先看代码:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        auto dummy = new ListNode(-1);
        dummy->next = head;
        ListNode* p  = dummy;
        if(head == NULL) return head;
        while(p->next)
        {
            if(p->next->val == val)
            {
                p->next = p->next->next;
            }
            else {
                p = p->next;
            }
        }
        return dummy->next;
    }
};

这道题目我觉得对于每一个学过计算机专业的人来说,这个题目的思路应该是融进血液里的,但是代码未必,我也很惭愧,作为一名计算机专业的研究生,熟悉思路,但是对于代码还是有很大欠缺。
下面我就默认看这篇文章的人熟悉这道题的思路了,不熟悉的话快去看数据结构,思路如果没有的话,那就该反思一下了。
首先我个人的习惯是上来就用双指针,因为这样我觉得对于一个单链表来说双指针更加容易找到它的前驱节点(名词不熟悉的也反思一下),但是这里我的方法超时了,所以就改成的单一指针。我前面做过一些关于图论的题目,发现一些解题规矩:
1.对于空指针一定要判断,不管是我前面做过的树的问题还是这里的单链表问题,都要注意首先判空
2.就是对于单链表一个比较模糊的地方就是头节点,目前单链表从头节点上我们可以分成两个派系:
1)头节点保存数据,就是1->2->3->4->null这种。
2)还有一种就是头节点不保存数据,head->1->2->3->4->null。这里的head我当时学的时候跟的是小甲鱼,他提到过,head是真正意义的头节点,我们看到的1这个节点,无论是带head还是不带head的都叫首元节点
有头节点的好处就是不用单独处理第一个元素了,因为head的存在可以使得首元节点的处理和其他元素一样,如果这里不明白还请记住,经过时间的沉积之后,你就会明白了。

707.设计链表

先看代码:

class MyLinkedList {
public:
    struct Node
    {
        int val;
        Node* next;
        Node(int _val): val(_val),next(NULL) {}
        Node() {}
    }*head;
    MyLinkedList() 
    {
        head = NULL;
    }
    int get(int index) {
       if(index < 0) return -1;
       auto p = head;
       for(int i = 0;i < index && p;i++) p = p->next;
       if(p == NULL) return -1;
       else return p->val;
    }
    
    void addAtHead(int val) {
       auto cur = new Node(val);
       cur->next = head;
       head = cur;
    }
    void addAtTail(int val) {
        if(!head) head = new Node(val);
        else {
            Node* p = head;
            while(p->next) p = p->next;
            p->next = new Node(val);
        }
    }
    void addAtIndex(int index, int val) {
      if(index <= 0) addAtHead(val);
      else
      {
          int len = 0;
          for(Node* p = head;p;p = p->next) len++;
          if(index == len) addAtTail(val);
          else if(index < len)
          {
              Node* p = head;
              for(int i = 0;i < index - 1;i++) p = p->next;
              auto cur = new Node(val);
              cur->next = p->next;
              p->next = cur;
          }
       }
    }
    void deleteAtIndex(int index) {
        if(index == 0) head = head->next;
        int len = 0;
        for(Node* p = head;p;p = p->next) len++;
        if(index > 0 && index < len)
        {
            Node* p = head;
            for(int i = 0;i < index - 1;i++) p = p->next;
            p->next = p->next->next;
        }
    }
};

这个其实在我写代码的时候就感觉是单链表的基本操作。但是还是那句老话,一看就会,一写就费,这次还好,不用看,思路会,一写费。不过这也是基本工,但是letcode的判断溢出的机制我也是醉了。
哦对,这里先讲一下为什么用单链表不用双链表,首先这些功能单双链表都能做,但是相比于单链表,双链表其实更加适合找前驱节点,这里的题目没有明显强调前驱的需要,所以用单链表更加简洁。
首先节点自己定义:至少有一个值,一个指针。
构造函数:就是初始化一个空节点,也是作为后续的头节点;
get(int index):这里提一嘴,既然是设计表,还是多多少少要考虑程序的健壮性的,所以这里对index会有一个判断,然后通过下表i,带着指针指向index所指的地方,这里我觉得很巧妙,因为我自己写的时候是创建一个count变量,当count++到等于index的时候才进行返回。
addAtHead(int val):头插法,直接指向head,然后将head指向新的节点。
addAtTail(int val):尾插法,直接遍历到最后,然后在最后插入即可
addAtIndex(int index, int val):我内存溢出在这里,这里确实要对index有一个判断,毕竟是在index的节点之前进行插入,而head本身没有前驱,所以,头节点要单独拿出来,这个时候会有朋友问:你前面不是说可以自己写一个空的头节点吗?是的,我确实说过,但是这里首先你看给的样例里面是没有头节点的,而且这里跟其他的题目不太一样的就是,这里的函数返回值类型是void,以前做过的都是返回类型是指针,所以这里就不能私自加空的头节点。如果index必链表长度更长就用尾插。其他时候就是用下表带动指针,然后进行插入。
deleteAtIndex(int index):这里的头节点也是要单独处理的,然后任仍然是下表带动指针,这里说一下,指针要停在index - 1的位置,因为你是删除index位置上的,这是单链表只能往后找,所以指针p到index-1的位置就要停下。

206.反转链表

先看代码:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* p = nullptr;
        ListNode* q = head;
        while(q)
        {
            ListNode* cur = q->next;
            q->next = p;
            p = q;
            q = cur;
        }
        return p;
    }
};

果然还是平时刷题刷的少。我开始觉得这个可以使用头插法,其实在考研的时候确实讲的用头插法做,但是letcode的检查机制会检查开始的时候建立一个空链表的指针不明,所以无法使用头插法。
这里先说第一种,双指针算法,也是正常思路的,就是逐个指针进行反转。这里看代码随想录更加方便:

文章讲解:https://programmercarl.com/0206.%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.html#%E6%80%9D%E8%B7%AF

第二种方法就是递归:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
       if(!head || !head->next) return head;
       auto tail  = reverseList(head->next);
       head->next->next = head;
       head->next = nullptr;
       return tail;
    }
};

这个代码大致思路我懂,就是先把head->next之后的链表先反转过来,最后处理head->next和head。
但是逐行代码我确实看不懂,有没有会的,请帮忙讲解一下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值