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

本文介绍了链表的基础理论、LeetCode中的三个链表相关题目(203移除链表元素、707设计链表、206翻转链表),包括虚拟头结点的应用、删除节点和添加节点的方法,以及双指针法和递归法在翻转链表中的应用。
摘要由CSDN通过智能技术生成

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

1、链表理论基础

文章链接:代码随想录 (programmercarl.com)

定义

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链表的入口节点称为链表的头结点也就是head。
链表1

类型
  1. 单链表
    指针域只能指向节点的下一个节点。
  2. 双链表
    每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
    既可以向前查询也可以向后查询。链表2
  3. 循环链表
    链首尾相连。
    可以用来解决约瑟夫问题。
    链表4
存储方式

数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

通过指针域的指针链接在内存中各个节点。

所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

下面的链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。
链表3

手写定义
//单链表
struct ListNode{
	int val;
	ListNode *next;
	ListNode(int v):val(v), next(NULL){}//节点的构造函数
};
//创建
ListNode* head = new ListNode(5);
链表的操作
  1. 删除节点
    复杂度O(1)。
    但是删除前需要先查找到这个元素,查找复杂度是O(n)。
    链表-删除节点
  2. 添加节点
    O(1)。
    链表-添加节点
性能

链表-链表与数据性能对比

2、LeetCode203.移除链表元素

题目链接:203. 移除链表元素 - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频讲解:手把手带你学会操作链表 | LeetCode:203.移除链表元素_哔哩哔哩_bilibili

在这里插入图片描述

第一想法

定义一个虚拟头结点,查找元素,比较,若相等则删除。
在这里插入图片描述

代码

犯错:

  • do-while的最后要有分号(因为不怎么写这个结构所以忘了);
  • 释放内存
ListNode* removeElements(ListNode* head, int val) {
        if(head==nullptr)
            return nullptr;
        ListNode* ans = new ListNode();//虚拟头节点,因为可能返回是空链表
        ans->next = head;
        ListNode* before = ans;
        ListNode* current = head;
        //遍历链表
        do{
            // cout<<"do:current->val="<<current->val<<endl;
            if(current->val == val){
                // cout<<"删除"<<endl;
                //删除
                ListNode* dele = current;
                current = before->next = current->next;
                delete dele;
            }
            else{
                cout<<"else"<<endl;
                before = before->next;
                current = current->next;
            }
        }while(current);

        return ans->next;
    }

3、LeetCode707.设计链表

题目链接:707. 设计链表 - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频讲解:帮你把链表操作学个通透!LeetCode:707.设计链表_哔哩哔哩_bilibili

实现功能
  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
代码

犯错:

  • 删除元素时考虑不周全:删除索引0的元素没被包含进去。
  • print()的话会超时

技巧/总结:

  • 遍历链表时,若要到达所给index前一个,LinkedNode* cur = _dummy_head;
    若要到达index,LinkedNode* cur = _dummy_head->next;
  • 遍历链表时,while(index--)的写法能够更简洁,而不用额外使用变量cur_idx记录。
class MyLinkedList {
public:
    struct LinkedNode{
        int val;
        LinkedNode* next;
        LinkedNode(int v):val(v), next(nullptr) {}
    };
    
    MyLinkedList() {
        //创建虚拟头节点
        _dummy_head = new LinkedNode(0);
        _size = 0;
        // print();
    }
    
    int get(int index) {
        print();
        //不合法索引
        if(index<0 || index >= _size)
            return -1;
        LinkedNode* current = _dummy_head->next;

        int cur_idx=0;
        //找到索引位置
        while(cur_idx != index){
            current = current->next;
            cur_idx++;
        }
        return current->val;
    }
    
    void addAtHead(int val) {
        LinkedNode* target = new LinkedNode(val);
        target->next = _dummy_head->next;
        _dummy_head->next = target;
        _size++;
        // print();
    }
    
    void addAtTail(int val) {
        LinkedNode* target = new LinkedNode(val);
        LinkedNode* cur = _dummy_head;
        //找到最后一个位置
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = target;
        _size++;
        // print();

    }
    
    void addAtIndex(int index, int val) {
        if(index == _size){
            addAtTail(val);
            return ;
        }
        if(index > _size)
            return ;
        if(index <=0 ){  //当index<0时,也插入到开头?为啥
            addAtHead(val);
            return ;
        }

        LinkedNode* target = new LinkedNode(val);
        LinkedNode* cur = _dummy_head;
        int cur_idx = -1;
        //找到索引的前一位置
        while(cur->next != nullptr){
            cur = cur->next;
            cur_idx++;
            if(cur_idx == index-1){
                target->next = cur->next;
                cur->next = target;
                _size++;
                return ;
            }
        }
        // print();
    }
    
    void deleteAtIndex(int index) {
        if(index<0 || index >= _size )
            return ;
        LinkedNode* cur = _dummy_head;
        int cur_idx = -1;
        //找到索引的前一位置。注意删除第一个元素的情况
        while(index --){
            cur = cur->next;
        }
        LinkedNode* dele = cur->next;
        cur->next = (cur->next)->next;
        delete dele;
        _size--;
        return ;
        // print();

    }
    void print(){
        LinkedNode* cur = _dummy_head;
        while(cur -> next != nullptr){
            cout<<cur->next->val;
            cur = cur->next;
        }
        cout<<endl;
    }
private:
    LinkedNode* _dummy_head;
    int _size;
};

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

4、LeetCode206.翻转链表

题目链接:206. 反转链表 - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频讲解:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili

法1 双指针法pre, cur

思路

初始化pre为nullptr,cur为head,每次遍历保存好临时变量,并做好翻转cur->next = pre
在这里插入图片描述

代码

犯错:

  • while中,一开始写的是cur->next != nullptr,这样当cur来到最后一个元素的时候,无法进入循环,这个元素就无法指到上一个元素
  • temp保存的应该是cur的next
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;
        ListNode* pre = nullptr;
        while(cur != nullptr){//注意这里不是cur->next
            ListNode* temp = cur->next;//注意这里temp是保存cur->next
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }

法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* reverse(ListNode* cur, ListNode* pre){
        if(cur == nullptr)//对应于while条件
            return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        return reverse(temp, cur);//对应于pre,cur往后移

    }
    ListNode* reverseList(ListNode* head) {
        //法2 递归
        return reverse(head, nullptr);//对应于pre,cur的初始化

    }
};

法3 从后往前翻转(不是很理解)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        // 边缘条件判断
        if(head == NULL) return NULL;
        if (head->next == NULL) return head;
        
        // 递归调用,翻转第二个节点开始往后的链表
        ListNode *last = reverseList(head->next);
        // 翻转头节点与第二个节点的指向
        head->next->next = head;
        // 此时的 head 节点为尾节点,next 需要指向 NULL
        head->next = NULL;
        return last;
    }
}; 

总结

  • 使用虚拟头节点–代码会简便很多
  • do-while结构
  • 释放内存
  • C++内置的链表用法——双向链表
  • while(index–)
#include <iostream>
#include <list>

int main() {
    std::list<int> myList;

    // 插入元素
    myList.push_back(1);
    myList.push_back(2);
    myList.push_back(3);

    // 遍历链表
    for (int value : myList) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    // 删除元素
    myList.remove(2);

    // 遍历链表
    for (int value : myList) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    return 0;
}
  • 32
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值