代码随想录-Day3| 203.移除链表元素,707.链表设计,206.反转链表

141 阅读5分钟

链表

插入/删除(时间复杂度)查询(时间复杂度)适用场景
数组O(n)O(1)数据量固定,频繁查询,较少增删
链表O(1)O(n)数据量不固定,频繁增删,较少查询
  • 注意要是删除第五个节点,链表需要从头结点查找到第四个,O(n)复杂度。
 // 单链表
 struct ListNode {
     int val;  // 节点上存储的元素
     ListNode *next;  // 指向下一个节点的指针
     ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
 };
 ListNode* head = new ListNode(5);
 // 不使用构造函数不能直接赋值
 ListNode* head = new ListNode();
 head->val = 5;

203 .移除链表元素

三种方法:

  • 普通,while current->next != NULL :删除的都是next,而不是对当前node操作。并且得独立处理head节点的case
  • my own method:while current != NULL: 删除的都是current,所以需要一个previous pointer来把两端连接起来,不需要独立处理head case,只需要while里多一个if
  • 虚拟头结点:

我自己的previous method

 /**
  * 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* current = head;
         ListNode* previous = NULL;
 ​
         while (current != NULL){
             if (current->val == val){
                 if (current == head){
                     head = current->next;
                     ListNode* temp = current;
                     current = current->next;
                     delete temp;
                 }else{
                     ListNode* temp = current;
                     current = current->next;
                     previous->next = current;
                     delete temp;
                 }
             }else{
                 previous = current;
                 current = current->next;
             }
         }
         return 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;
             }
         }
         head = dummyHead->next;
         delete dummyHead;
         return head;
     }
 };
 ​
  • 注意点:

    • 如果while current->next != NULL: 永远对下一个节点进行操作的话,删除以后,不需要将current推动到下一位,因为我们的current->next是在变的,所以把持原样就行。只在没有删除节点时,手动更新currentcurrent->next

707

  • 经过我的不断探索,发现还是得有虚拟头结点,不然有很多case很难处理
 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;
         _size--;
     }
 ​
     // 打印链表
     void printLinkedList() {
         LinkedNode* cur = _dummyHead;
         while (cur->next != nullptr) {
             cout << cur->next->val << " ";
             cur = cur->next;
         }
         cout << endl;
     }
 private:
     int _size;
     LinkedNode* _dummyHead;
 ​
 };

总结:

  • 虚拟头结点很重要 加单链表
  • 不用tail的话,delete at tail 和 add at tail复杂度会高,但是没办法
  • 用tail就得双链表
  • 然后删除节点时,要删temp,不能把原本还要用的指针给删了。

206

递归法

 class Solution {
 public:
     ListNode* reverseList(ListNode* head) {
         if (head == NULL){ //边界判断
             return head;
         }else if (head->next == NULL){
             return head; // 此时head为last节点,return base case
         } 
         // last node保持不变,一直传递回去
         ListNode* last_node = reverseList(head->next);
         // reverse order
         head->next->next = head;
         head->next = NULL;
         return last_node;  
        
     }
 };

想出了递归的架构,但是在处理last_node上没琢磨出来,最后发现直接return最后一个个就可以了

  • 解题过程:最终一定是return最后的节点的,这个在一开始就要确立好,然后再想,在此基础上怎么做才能又return了last又能让递归进行下去,我之前想要return head是想next_node = reverseList(head->head);这样一个比较帅的操作,在该节点call function时同时还获取了next的节点,但是这就是脱裤子放屁啊,虽然你是通过递归获得了,但是直接的head->next不就搞定了吗。这次的矛盾就是在一旦head->next->给到了其他地址,在普通的循环里,就无法往下迭代推进了。但是利用递归这个反向操作,我应该明白递归最后函数的收束是从结尾开始的,那么只要在call递归的那一行之后进行操作就没问题了。 最后就是头结点的next得接到null上,这个通过思考应该能知道head->next = NULL 在除了首节点以外的节点都会被递归里的覆盖的。应该多画图理解的。
  • 注意第6行,一开始是把两个if case 放一块判断的,但是当head == NULL时,先判断了head->next == NULL就会出现undefinedBehavior,在null上进行操作了。

循环

 class Solution {
 public:
     ListNode* reverseList(ListNode* head) {
         ListNode* pre = NULL; // 不能设为 head->pre,struct里未定义
         while(head != NULL){ // 判断的都是当前的case
             ListNode* temp = head->next; 
             // 因为调换了,用temp记录下一个iterator
             if (pre != NULL){ 
                 head->next = pre; // 否则就调换为pre,所以在下面需要更新pre
             }else{ // 只有head的pre为NULL
                 head->next = NULL;
             }
             if (temp == NULL){
                 return head;
             }
             // pre永远记录上一个循环的指针
             pre = head;
             // iterator 更新为 head->next
             head = temp;
         }
         return head;
         
     }
 };

总结:链表还是掌握的可以的,两个都写出来了。