(3/60)移除链表元素、设计链表、反转链表

移除链表元素

leetcode:203. 移除链表元素

原链表操作法

思路

在原链表上操作,分为头节点操作和非头结点操作。

  1. 循环检测head是不是val,是的话删除结点并后移head指针。
  2. 循环检测head之后的结点是不是val(用cur指针),是的话删除结点;否则指针后移遍历链表。
  3. 重复1、2直至head==NULLhead->val != val(头节点为空或头节点不等于val)、cur==NULLcur->next==NULL(循环指针结点为空或者循环指针的下一结点为空)。

复杂度分析

时间复杂度:O(N)。两个并列while,而且其实总体只会有链表长度次操作。

空间复杂度:O(1)。常数级额外空间(1个ListNode)。

注意点

  1. 最后返回head。原链表操作,head就是头节点

代码实现

/**
 * 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* tmp = NULL;
        // 处理头结点
        while(head != NULL && head->val == val){
            tmp = head;
            head = head->next;
            delete tmp;
        }
        // 处理非头结点
        ListNode* cur = head;   // 遍历指针
        while(cur != NULL && cur->next != NULL ){
            // 是目标则删除,否则指针后探
            if(cur->next->val == val){
                tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            }else{
                cur = cur->next;
            }
        }

        return head;    // 原链表操作,head就是头节点
    }
};

虚拟头结点法

思路

在原链表基础上,新创建一个虚拟头结点,统一了结点操作。(不需要顾虑头指针问题,原链表操作要解决的最大问题删除头指针结点时如何不失去头指针,有了虚拟头结点后就不怕丢失头节点指针了,dummyHead->next永远是头节点)

复杂度分析

时间复杂度:O(N)。两个并列while。

空间复杂度:O(1)。常数级额外空间(1个ListNode)。

注意点

  1. cur要从dummyHead开始。因为实际的操作是从cur->next开始的,如果从head开始则会掠过head。(与原链表操作对应,左侧cur是开区间。在原链表操作中,操作分为head和非head,head处理过后,cur指向了head,实际处理时其实是从cur->next开始的)

代码实现

/**
 * 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* dummyHead = new ListNode();   // 虚拟头节点,方便统一节点操作
        ListNode* cur = NULL;   // 循环指针,用于遍历链表
        ListNode* tmp = NULL;   // 临时指针,用于释放结点内存
        dummyHead->next = head;
        cur = dummyHead;
        while(cur != NULL && cur->next != NULL){
            // 若等于val则删除该结点;否则向后遍历
            if(cur->next->val == val){
                tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            }else{
                cur = cur->next;
            }
        }

        return dummyHead->next;     // 虚拟头结点的next才是头节点
    }
};

设计链表

leetcode:707. 设计链表

虚拟头节点法

思路

复杂度分析

注意点

  1. 初始化列表语法LinkedNode(int val) : val(val),next(nullptr){/* 构造函数函数体 */};

  2. 用下标和_size控制边界。

  3. 查、改结点的方法,需要直接指向目标结点的指针。get()方法cur指向下标为index的结点,初始化为dummyHead->next

  4. 增、删结点的方法,需要得到目标结点前一个位置的指针。addAtHeadaddAtTailaddAtIndexdeleteAtIndex都是cur指向处理元素的前一个(头结点的前一个、尾结点的前一个、下标index结点的前一个)。

    (其实全都可以是到目标节点的上一个,然后需要查、改时往后伸就可以了)

代码实现

class MyLinkedList {
public:
    struct LinkedNode{
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val),next(nullptr){};   // 初始化列表语法,简便的写法
    };

    // 用的是index控制

    MyLinkedList() {
        _size = 0;
        _dummyHead = new LinkedNode(0);
    }
    
    int get(int index) {
        // cur指向下标为index的结点
        int result = -1;
        LinkedNode* cur = _dummyHead->next;
        // index: 0 ~ size-1
        if(index < _size){
            for(int i = 0;i < index;i++){
                cur = cur->next;
            }
            result = cur->val;
        }

        return result;
    }
    
    void addAtHead(int val) {
        LinkedNode* tmp = _dummyHead->next;
        LinkedNode* newNode = new LinkedNode(val);  // * 这个不是vector,需要new对象

        _dummyHead->next = newNode;
        newNode->next = tmp;
        _size++;
    }
    
    void addAtTail(int val) {
        LinkedNode* cur = _dummyHead;   // 直接指向dummyHead
        for(int i = 0;i < _size;i++){   
            cur = cur->next;
        }
        LinkedNode* newNode = new LinkedNode(val);
        cur->next = newNode;
        _size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index <= _size){
            LinkedNode* cur = _dummyHead;   // 直接指向dummyHead
            for(int i = 0;i < index;i++){   
                cur = cur->next;
            }
            LinkedNode* tmp = cur->next;
            LinkedNode* newNode = new LinkedNode(val);
            cur->next = newNode;
            newNode->next = tmp;
            _size++;
        }
    }
    
    void deleteAtIndex(int index) {
        // 删除结点,cur指向目标结点前一个
        if(index < _size){  // 下标有效
            LinkedNode* cur = _dummyHead;
            for(int i = 0;i < index;i++ ){
                cur = cur->next;
            }
            LinkedNode* tmp = cur->next;
            cur->next = cur->next->next;
            delete tmp;
            _size--;
        }
    }
private:
    int _size;
    LinkedNode* _dummyHead;
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

反转链表

leetcode:206. 反转链表

双指针法

思路

核心就是,从前往后反转指针的指向。cur->next = pre;

  1. 设置当前指针cur、前指针pre,将cur下一结点改为指向pre
  2. curpre借助tmp向后移动,重复1,直至cur==NULL

复杂度分析

时间复杂度:O(N)。

空间复杂度:O(1)。

注意点

  1. 需要引入"第三只杯子"tmp。
  2. 结束条件是cur==NULL
  3. 最后返回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;
        ListNode* tmp = NULL;
        while(cur){
            tmp = cur->next;
            cur->next = pre;

            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

递归法

思路

和双指针法类似,关注传入参数、函数体核心做的操作、返回值。

  1. 传入参数是cur、pre,初始值是head、NULL。
  2. 递归核心操作

复杂度分析

时间复杂度:O(N)。遍历链表一遍。

空间复杂度:O(N)。调用系统栈,最坏情况下系统栈深度为N。

注意点

  1. 递归首先写退出条件。
  2. 传入参数(形参)有cur、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) {
        return reverse(head,NULL);
    }

    ListNode* reverse(ListNode* cur,ListNode* pre){
        if(cur == NULL){ return pre; }
        ListNode* tmp = cur->next;
        cur->next = pre;
        return reverse(tmp,cur);
    }
};
posted @ 2024-01-27 01:21  Tazdingo  阅读(2361)  评论(0)    收藏  举报