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

目录

 链表理论基础

链表基础

链表的定义

对比链表与数组

203.移除链表元素 

思路

代码实现

总结

707.设计链表

思路

代码实现

总结

206.反转链表

思路

1.第一直觉

2.双指针法

3.递归法

代码实现

1.第一直觉

2.双指针法

3.递归法

总结


 链表理论基础

链表基础

众所周知的,链表是线性结构,每个结点有数据域和指针域,head指针指向头节点,尾结点指针指向null;

各结点地址空间不连续;

类型有单链表、双链表、循环链表等;

链表的定义

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

若不定义构造函数,C++也会默认生成一个构造函数,不过是默认不含参的。 

对比链表与数组

代码随想录算法训练营第三天 | 链表理论基础,203.移除链表元素,707.设计链表,206.反转链表_第1张图片

203.移除链表元素 

题目链接:203.移除链表元素

思路

遍历链表,若当前结点值匹配,则删除当前结点,此时需要当前结点的前一结点,所以需要记录下当前结点的前一结点,以便删除;但是在实际执行中发现,这样还需要在指针跨越记录当前结点后内存中删除结点,需要再设结点用于删除,实现下来比较杂乱,而且总是会出这样或那样的问题。

看过解析以后,发现可以设置一个头结点,使得对首结点的操作与其他结点一样,这一下就把以前书上课上学过的知识串了起来,实现起来简便了很多。

代码实现

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* pre = new ListNode(0, head);
        ListNode* prehead = pre;
        while(pre -> next != NULL){
            if(pre -> next -> val == val){
                ListNode* temp = pre -> next;
                pre -> next = pre -> next -> next;
                delete temp;
            }else{
                pre = pre -> next;
            }
        }

        head = prehead -> next;
        return head;
    }
};

总结

为了方便对首结点进行操作,可添加空的头结点。

707.设计链表

题目链接:707.设计链表

思路

了解了链表基础理论后,思路很清晰,但是一些具体实现方法不太清楚,比如初始化如何实现;

查看解析以后发现,设计链表类并不是链表,需要在类中给出链表的表示方法,类中函数是用来操作链表的,弄明白了这一点,其他的就没什么太大问题了。

代码实现

class MyLinkedList {
private:
    ListNode* prehead;  // 虚拟头结点
    int size;   // 链表长度

public:
    struct ListNode{
        int val;
        ListNode* next;
        ListNode(int x): val(x), next(NULL){}
    };

    MyLinkedList() {
        prehead = new ListNode(0);
        size = 0;
    }
    
    int get(int index) {
        if(index >= size || index < 0)   return -1;

        ListNode* cur = prehead->next;
        while(index--){
            cur = cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        ListNode* temp = new ListNode(val);
        temp->next = prehead->next;
        prehead->next = temp;
        ++size;
    }
    
    void addAtTail(int val) {
        ListNode* add = new ListNode(val);
        ListNode* cur = prehead;
        while(cur->next != NULL)  cur = cur->next;
        cur->next = add;
        ++size;
    }
    
    void addAtIndex(int index, int val) {
        if(index < size){
            ListNode* add = new ListNode(val);
            ListNode* pre = prehead;
            while(index--)  pre = pre->next;
            add->next = pre->next;
            pre->next = add;
            ++size;
        }else if(index == size) addAtTail(val);
    }
    
    void deleteAtIndex(int index) {
        if(index < size){
            ListNode* pre = prehead;
            while(index--)  pre = pre->next;
            ListNode* temp = pre->next;
            pre->next = pre->next->next;
            delete temp;

            // 查看解析后发现如下问题:
            //delete命令指示释放了tmp指针原本所指的那部分内存,
            //被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
            //如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
            //如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
            
            temp=nullptr;

            --size;
        }

    }
};

总结

这一题学到了一下几点:

        一是链表类中,需要给出链表的结构体定义;

        二是删除临时结点时,也要将指针进行置空,以防其乱指;

        三是变量最好封装起来;

206.反转链表

题目链接:206.反转链表

思路

1.第一直觉

拿到题目,我的思路是,将首结点后面的结点一个一个插到“首结点”前面,最终实现反转,可以新建一条链表,或者直接在本链表上实现,我感觉可能新建一条链表更简便。

但是我试了好久始终不能成功,问了下人工智能发现原来是每次中间暂存结点都被我delete了,实际上只改变连接关系的指针不需要delete,只有删除结点时需要delete。

第一直觉往往都是错的,虽然确实做出来了但是时间和空间情况都不太理想。

2.双指针法

看过解析以后发现,可以设置双指针,直接将next指针指向反转,从而实现链表反转。

有了方向以后,实现还是很容易的。

3.递归法

思路和双指针大致相同。

代码实现

1.第一直觉

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prehead = nullptr; // 新链表头结点
        ListNode* cur = head;
        while(cur != NULL){
            ListNode* temp = cur;
            cur = cur->next;
            temp->next = prehead;
            prehead = temp;
        }

        return prehead;
    }
};

2.双指针法

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

3.递归法

class Solution {
public:
    ListNode* reserve(ListNode* pre, ListNode* cur){
        if(cur != nullptr){
            ListNode* tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }else{
            return pre;
        }
        return reserve(pre, cur);
    }

    ListNode* reverseList(ListNode* head) {
        return reserve(NULL, head);
    }
};

总结

新设链表实现反转,实际上是对内存空间的浪费;但是直接反转指针的思路怎么想到呢,我认为是要观察反转前后的链表的关系,并且积累经验。双指针的思想我想应该总结一下什么情况的问题用双指针来解。

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