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

203.移除链表元素

题目链接:Leetcode
文章讲解:代码随想录
视频讲解:bilibili

解题思路

  本题的关键在于理解链表的存储方式,“锁链”一般的元素关系。由于之前使用c语言实现的,C++实现有的不熟练,删除链表节点不能用free()而要用delete,因为链表节点是new创建的对象。

解法1

  这里容易出错的是头节点处的删除操作,因为可能存在头节点开始的连续几个节点值都为val,因而用的是while而不是if,本解法直接对链表本身进行操作。

/**
 * 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) {
        while(head != NULL && head->val==val){
            ListNode* p = head;
            head = head->next;
            delete p;
        }
        ListNode* cut = head;
        while(cut!=NULL && cut->next!=NULL){
            if(cut->next->val==val){
                ListNode* p = cut->next;
                cut->next = p->next;
                delete p;
            } else{
                cut = cut->next;
            }
        }
        return head;
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
解法2

  使用虚设头节点的方法进行操作,这里的虚拟头节点在其他地方也被称为头指针,即指向头节点的指针,这么做的好处在于可以将头节点的删除操作与其余节点的删除操作统一起来,简化了实现逻辑。

/**
 * 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* headptr = new ListNode(0, head);
        ListNode* current_node = headptr;
        while(current_node->next != nullptr){
            if(current_node->next->val==val){
                ListNode* p = current_node->next;
                current_node->next = p->next;
                delete p; 
            } else{
                current_node = current_node->next;
            }
        }
        head = headptr->next;
        delete headptr;
        return head;
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

707.设计链表

题目链接:Leetcode
文章讲解:代码随想录
视频讲解:bilibili

解题思路

  对于链表这一数据结构功能实现较为综合的一道题目,注重查找、添加和删除等基本操作,结构定义方面C++的类初始化方式与python中的类定义初始化方式类似,C++会稍微复杂一些,可以类比学习。
  对于单向链表,由于每个节点只记录下一个节点的地址,因此没有回溯的功能,在删除和插入操作时需要记录前一个节点,以方便处理。

初版
class MyLinkedList {
public:
    struct LinkNode{
        int val;
        LinkNode* next;
        LinkNode(int val): val(val), next(nullptr){}
    };
    MyLinkedList() {
        _headptr    = new LinkNode(0);  // 虚拟头节点,功能与头指针相同
        _length      = 0;  
    }
    
    int get(int index) {
        if(index >= _length || index < 0)
            return -1;
        else{
            LinkNode* current_node = _headptr;
            for(int i=0; i <= index; i++)
                current_node = current_node->next;
            return current_node->val;
        }
    }
    
    void addAtHead(int val) {
        LinkNode* new_node = new LinkNode(val);
        new_node->next = _headptr->next;
        _headptr->next = new_node;
        _length++;
    }
    
    void addAtTail(int val) {
        LinkNode* new_node = new LinkNode(val);
        LinkNode* current_node = _headptr;
        while(current_node->next != nullptr)
            current_node = current_node->next;
        current_node->next = new_node;
        _length++;
    }
    
    void addAtIndex(int index, int val) {
        if(index > _length)
            return ;
        LinkNode* new_node = new LinkNode(val);
        if(index<=0){
            new_node->next = _headptr->next;
            _headptr->next = new_node;
            _length++;
        } else if(index == _length){
            LinkNode* current_node = _headptr;
            while(current_node->next != nullptr)
                current_node = current_node->next;
            current_node->next = new_node;
            _length++;
        } else{
            LinkNode* current_node = _headptr;
            LinkNode* pre_node = current_node;
            for(int i=0; i<=index; i++){
                pre_node = current_node;
                current_node = current_node->next;
            }
            pre_node->next = new_node;
            new_node->next = current_node;
            _length++;
        }
    }
    
    void deleteAtIndex(int index) {
        if(index < 0 || index >= _length)
            return;
        else if(index==0){
            LinkNode* p = _headptr->next;
            _headptr->next = p->next;
            delete p;
            _length--;
        } else{
            LinkNode* current_node = _headptr;
            LinkNode* pre_node = current_node;
            for(int i=0; i<=index; i++){
                pre_node = current_node;
                current_node = current_node->next;
            }
            pre_node->next = current_node->next;
            delete current_node;
            _length--;
        }
    }
    private:
        LinkNode* _headptr;
        int _length;
};

/**
 * 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);
 */
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

💡对于为什么使用指针类型定义节点而不用结构体类型本身定义节点,笔者目前想到的理由是在书写代码时若用的是结构体类型则需用&来获取地址,指针则较为简洁,但肯定有更加深层次的理由,待领悟后再来补充。

206.反转链表

题目链接:Leetcode
文章讲解:代码随想录
视频讲解:bilibili

解题思路

  本题需要将链表反转,如果是数组,反转最直接的思路就是定义一个新的数组,从尾到头将原数组的值复制给新数组,而本题也可用这种思路,借鉴上面那题从头插入节点的方式,从头遍历原链表,并用每个节点对应的值新建立一个节点,从头插入新链表。遍历结束后,得到的新链表即为反转链表。

解法1
/**
 * 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* headptr = new ListNode(0);
        ListNode* current_node = head;
        while(current_node!=nullptr){
            ListNode* p = new ListNode(current_node->val, headptr->next);
            headptr->next = p;
            current_node = current_node->next;
        }
        ListNode* reverse_head = headptr->next;
        delete headptr;
        return reverse_head;
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
解法2

  解法1过于简单粗暴,在时间上不占优的同时空间上也有不小开销,因此更好的解法还是对原链表本身进行操作,使用双指针的方式让链表中的节点转换指向,需要用到三个指针cur用于指向当前节点,pre用于指向原链表顺序中的上一个节点,p用于指向原链表顺序中cur的下一个节点,用于更新cur

/**
 * 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 = nullptr;
        ListNode* cur = head;
        ListNode* p = cur;
        while(cur!=nullptr){
            p = cur->next;
            cur->next = pre;
            pre = cur;
            cur = p;
        }
        return pre;
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
解法3

  最后递归解法,递归是程序设计中一大理解难点,很多递归程序都是自底向上的,有点反直觉而一时不好接受。本题的递归初始条件倒是从头开始的,可以借此机会尝试理解递归思想。递归函数的主体部分其实做的就是将链表节点的指向反转,并更新precur,原理是一样的,只是写成了递归形式。

/**
 * 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* reverse(ListNode* pre, ListNode* cur){
        if(cur==nullptr)    return pre;
        ListNode* p = cur->next;
        cur->next = pre;
        pre = cur;
        cur = p;
        
        return reverse(pre, cur);
    }
    ListNode* reverseList(ListNode* head) {
        return reverse(nullptr, head);
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

  本解法的空间复杂度为 O ( n ) O(n) O(n),且由于函数调用需要更多的时间开销,因此在执行时间上是不如解法2甚至不如解法1的,不是说递归听起来高大上就一定实用,也要看应用场景。不过,用递归写出的程序往往是所有解法中最简洁的。

今日总结

  这几天外出比赛没有什么时间coding,这次的题目之前有过用C语言python写相关代码的经历,用C++写起来除了前期适应面向对象的语法问题之外都还算顺利。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值