代码随想录算法训练营Day03 | LeetCode203.移除链表元素、LeetCode707.设计链表、LeetCode206反转链表

131 阅读5分钟

LeetCode203. 移除链表元素

题目链接:leetcode.cn/problems/re…

思路:想到先设置一个虚拟头结点,指向该链表头结点,再进行删除操作。

C++代码如下(错误!):

/**
 * 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(0);  //设置一个虚拟头结点,初始化节点为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;
            }

            cur = cur->next;   //更新cur  //此处逻辑有问题!!

        }
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

以上是我原来的写法,跑的时候发现执行出错了,报错:Line 20: Char 21: runtime error: member access within null pointer of type 'ListNode' (solution.cpp)。

后面我自己模拟走了一遍,发现按照上面的写法,在判断条件cur->next != NULL中,我只能判断cur->next不为空,我没法判断cur->next->next是否为空,但是倘若进入if(cur->next->val == val)结构体中,会执行cur->next = cur->next->next语句,也就是说此时cur的后继节点变成了cur->next->next,那么我在下面更新cur时就是直接把cur更新到cur->next->next,但是在上面我只判断了cur->next是否为空,并没有判断cur->next->next是否为空,这就会出现cur->next->next为空的情况下被更新为cur,也就是cur变成NULL了

修改如下(正确!):

/**
 * 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(0); // 设置一个虚拟头结点,初始化节点为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;  //更新cur
            }
            
        }
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

LeetCode707. 设计链表

题目链接:leetcode.cn/problems/de…

思路:这一题其实主要考察对链表的常见操作,我是设置一个虚拟头结点再进行操作,这样更加方便。

C++代码如下:

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个节点就是头结点!!头节点后面的那结点才是第1个结点。
    int get(int index) 
    {
        if((index < 0))
        {
            return -1;
        }
        LinkedNode* cur = _dummyHead->next; //这个地方一开始不太理解,为啥没有_dummyHead->next=head这句代码呢?如果不将虚拟头结点指向head,后面怎么移动怎么读取val?再说了,_dummyHead->next不定义的话它指的是啥咱也不知道啊?   //后面弄明白了,就在上面定义的结构体的构造函数里,我已经初始化了_dummyHead变量,那么_dummyHead->next就是null了,其实也是有定义了。

        while(index--)
        {
            cur = cur->next;
        }
        return cur->val;

    }
    
    void addAtHead(int val) //添加值为val的节点,作为链表的头节点
    {
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = _dummyHead->next;
        _dummyHead->next = newNode;
        _size++;
    }
    
    void addAtTail(int val) //添加值为val的节点,作为链表的尾节点
    {
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(cur->next != NULL)
        {
            cur = cur->next;
        }
        newNode->next = NULL;
        cur->next = newNode;
        _size++;
    }
    
    void addAtIndex(int index, int val)  //在链表的第index个节点之前添加值为val的节点
    {
        if(index > _size)
        {
            return;
        }
        if(index < 0)
        {
            index = 0;
        }

        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while((index--) && (cur->next != NULL))
        {
            cur = cur->next;
            
        }
        newNode->next = cur->next;
        cur->next = newNode;
        _size++;

        //return _dummyHead->next;    //没说要返回值

    }
    
    void deleteAtIndex(int index) 
    {
        if(index >= _size)
        {
            return;
        }
        if(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 != NULL)
        {
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout << endl;
    }

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);
 */

LeetCode206. 反转链表

题目链接:leetcode.cn/problems/re…

思路:一开始想直接反转,但是有些绕,没写出来。后来看了题解,使用双指针法

首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。

image.png

C++代码如下:

/**
 * 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* pre = NULL;
        ListNode* cur = head;
        while(cur != NULL)
        {
            ListNode* tmp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre;  // 翻转操作
            pre = cur;     // 更新pre 和 cur指针
            cur = tmp;
        }
        return pre;
    }
};

分析:注意在while循环体判断时,判断条件是cur != NULL,我原本写的是cur->next != NULL,然后循环体外写的是 return cur,会报错,后来分析了一下:因为head有可能为NULL,那么cur = head也为NULL了,那就谈不上cur->next != NULL,所以报错。后面改成判断cur != NULL,以及改成return pre 就通过了。

今日总结:

  1. LeetCode707. 设计链表 要多加练习!写的不够熟练。
  2. LeetCode206. 反转链表可以拓展思维,可以多用一个指针来辅助,使用双指针法,直接写的话很绕。