代码随想录第三天 | 链表理论基础:对链表的增删查(leetcode 203,707);反转链表(leetcode 206)

1、链表理论基础

整理/图片来源——链表理论基础 代码随想录

1.1 链表的分类

代码随想录第三天 | 链表理论基础:对链表的增删查(leetcode 203,707);反转链表(leetcode 206)_第1张图片

图1.1 单链表

链表可以分为单链表,双链表和循环链表。
单链表如图1.1所示
双链表既可以向前查询也可以向后查询,如图1.2所示
代码随想录第三天 | 链表理论基础:对链表的增删查(leetcode 203,707);反转链表(leetcode 206)_第2张图片

图1.2 双链表

循环链表,链表首尾相连,如图1.3所示
代码随想录第三天 | 链表理论基础:对链表的增删查(leetcode 203,707);反转链表(leetcode 206)_第3张图片

图1.3 循环链表

1.2 链表的存储

数组是在内存中是连续分布的,但是链表在内存中不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。

1.3 链表的定义与操作

链表的定义:

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

构造函数初始化节点:

ListNode* head = new ListNode(5);

链表的操作:
删除/添加节点(如图2.1/2.2所示):
代码随想录第三天 | 链表理论基础:对链表的增删查(leetcode 203,707);反转链表(leetcode 206)_第4张图片

图2.1 删除链表节点

代码随想录第三天 | 链表理论基础:对链表的增删查(leetcode 203,707);反转链表(leetcode 206)_第5张图片

图2.2 插入链表节点

1.4 操作性能分析(与数组对比)

代码随想录第三天 | 链表理论基础:对链表的增删查(leetcode 203,707);反转链表(leetcode 206)_第6张图片

2、对链表的操作(增删查)

2.1 leetcode 203:删除链表节点

/**
 * 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* pre = head;不能放在头上不然[7,7,7,7] 7会报错
        while(head != nullptr && head->val == val) {//不是if是while,如果出现开头连续都是目标字符
        //所有使用head->val/next都要先判断head是否可能为空
            ListNode* cur = head;
            head = head->next;
            delete cur;
        }
        ListNode* pre = head;
        while(pre != nullptr && pre->next != nullptr) {
            if(pre->next->val == val) {
                ListNode* cur = pre->next;
                pre->next = pre->next->next;//已经换到下一个节点了不需要pre = pre->next;
                delete cur;//别忘了删多余节点
            }
            else {
                pre = pre->next;
            }
            //pre = pre->next;
        //报错:runtime error: member access within null pointer of type 'ListNode' (solution.cpp)
        //如果这里走到最后一个元素了,pre就为空了,进入while会对空指针操作(pre->next),所以对pre是否为空要在while里面有个判断
        }
        return head;
    }
};

删除链表节点有两种办法:

1、直接使用原来的链表来进行删除操作。(第一次的代码)
2、设置一个虚拟头结点在进行删除操作。(后面的代码)

对于1,移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。所以头结点移除要将头结点向后移动一位。在写代码的时候也会发现,需要单独写一段逻辑来处理移除头结点的情况

对于2,设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。return 头结点的时候,别忘了 return dummyNode->next;, 这才是新的头结点

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummy = new ListNode(0);//新建的时候new ListNode返回的为指针
        dummy->next = head;
        ListNode* pre = dummy;
        while(pre != nullptr && pre->next != nullptr) {
//其实这里由于虚拟头节点肯定不为空,pre != nullptr 可以删了
            if(pre->next->val == val) {
                ListNode* cur = pre->next;
                pre->next = pre->next->next;
                delete cur;
            }
            else {
                pre = pre->next;
            }
        }
        head = dummy->next;//虚拟头节点别忘了删
        delete dummy;
        return head;
    }
};

2.2 leetcode 707:带虚拟头节点的增删查

第一遍代码:

class MyLinkedList {
public:
    struct LinkedNode
    {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}//{}别忘了
    };
    
    MyLinkedList() {
        //加入虚拟头节点更方便,统一操作
        dummy = new LinkedNode(0);
        size = 0;
    }
    
    int get(int index) {
        LinkedNode* cur = dummy;
        if(index < 0 || index >= size) {
            return -1;
        }
        for(int i = 0; i <= index; i++) {
            cur = cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkedNode* tmp = new LinkedNode(val);
        tmp->next = dummy->next;
        dummy->next = tmp;
        size++;
    }
    
    void addAtTail(int val) {
        LinkedNode* tmp = new LinkedNode(val);
        LinkedNode* cur = dummy;
        while(cur->next != nullptr) {
            cur = cur->next;
        }
        cur->next = tmp;
        size++;
    }
    
    void addAtIndex(int index, int val) {
        LinkedNode* tmp = new LinkedNode(val);
        LinkedNode* cur = dummy;
        if(index < 0 || index > size) return;
        while(index--) {
            cur = cur->next;
        }
        tmp->next = cur->next;
        cur->next = tmp;
        size++;
    }
    
    void deleteAtIndex(int index) {
        LinkedNode* tmp;
        LinkedNode* cur = dummy;
        if(index < 0 || index >= size) return;
        while(index--) {
            cur = cur->next;
        }
        tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        size--;
    }

private:
    LinkedNode* dummy;//**定义全局变量
    int size;//要加一个大小不然不方便
};

3、反转链表

3.1 leetcode 206

每次某个节点要->next的时候想一想该节点可不可能为空
第一遍代码:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == nullptr) return head;//每次要->next的时候想一想可不可能为空
        ListNode* pre = head;
        ListNode* cur = head->next;//当时需要记录几个节点,保证完成任务的同时可以走到下一个
        head->next = nullptr;
        while(cur != nullptr) {
            ListNode* nextnode = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nextnode;
        }
        return pre;
    }
};

pre可以直接搞成空的,这样可以少判断一个是否为空节点

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;
    }
};

改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。首先要把 cur->next 节点用tmp指针保存一下,也就是保存下一个要操作的节点

你可能感兴趣的:(leetcode,c++,链表,leetcode,c++,数据结构,算法)