第二章链表1

链表

  1. 链表相关知识

    C/C++的定义链表节点方式,如下所示:

    struct ListNode
    {
        int val;//节点值
        ListNode *next;//指向下一个节点地址
        ListNode():val(x),next(null){}//有参构造函数
    };
    

    如果不定义有参构造函数,默认构造函数无参,在初始化链表时不能给值,需要分两步

    ListNode *head=new ListNode();//堆区创建一个链表头节点,new返回的是链表地址,需要用指针去接收
    head->val=5;//头节点的值赋值为5
    

    链表与数组性能对比

    性能分析

    再把链表的特性和数组的特性进行一个对比,如图所示:

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

  2. 203移除链表元素

    1)题目描述

    给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

    示例 1:

    img

    输入:head = [1,2,6,3,4,5,6], val = 6
    输出:[1,2,3,4,5]
    

    2)解题思路

    链表删除时分两种情况,如果删头节点,直接令head=head->next,然后删除原有头节点。

    若为非头节点,需要先设置一个当前节点,当前节点从头开始遍历,依次判断当前节点下一个节点是否为要删除的节点。如果是,那么将下一节点地址暂存,当前节点的next指向下下节点,然后删除暂存的节点。

    3)代码实现

    class Solution {
    public:
        ListNode* removeElements(ListNode* head, int val) {
    
            //删除头节点
            while(head!=nullptr&&head->val==val){
                ListNode *tmp=head;//暂存头节点地址
                head=head->next;//头节点指向下一个节点
                delete tmp;//删除原始的头节点
            }
            //删除非头节点
            //定义一个当前节点
            ListNode *cur=head;//当前节点从头节点开始遍历
            while (cur!=nullptr&&cur->next != nullptr)//遍历链表,如果指向下一个节点指针地址不为空
            {
                if (cur->next->val == val)//如果下一节点值等于目标值
                {
                    ListNode * tmp = cur->next;//暂存的是等会要清空的节点
                    cur->next = cur->next->next;//更新后的cur-next可能指空,因此while中要判cur-next
                    delete tmp;
                }
                else//如果不等,指针移到下一个节点
                {
                    cur = cur->next;//更新后的cur可能指空
                }
            }
            return head;      
        }
    };
    

    4)总结

    链表删除需要注意三点:一,区分头节点和非头节点删除方法不同,二,取链表属性前需要判断指向是否为空,若为空是不能访问值的;三,tmp暂存的是需要删除的节点,并且delete操作也删除的是tmp。

    虚拟头节点的方法可以统一头节点和非头节点的代码,但是逻辑太绕,就不研究了,待熟悉链表编程后再更新。

  3. 707设计链表

    1)题目描述

    设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:valnextval 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

    在链表类中实现这些功能:

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

    示例:

    MyLinkedList linkedList = new MyLinkedList();
    linkedList.addAtHead(1);
    linkedList.addAtTail(3);
    linkedList.addAtIndex(1,2);   //链表变为1-> 2-> 3
    linkedList.get(1);            //返回2
    linkedList.deleteAtIndex(1);  //现在链表是1-> 3
    linkedList.get(1);            //返回3
    

    2)解题思路

    草稿纸上画链表结构图,按照操作步骤实现代码。

    3)代码实现

    class MyLinkedList {
    public:
    //创建链表数据结构
        struct ListNode
        {
            int val;
            ListNode *next;
            ListNode(int x):val(x),next(nullptr){}
        };
        //初始化链表,创建虚拟头节点
        MyLinkedList() {
            dummyHead=new ListNode(0);
            m_size=0;
        }
        
        int get(int index) {
            if(index>(m_size-1)||index<0)
            {
                return -1;
            }
            ListNode *cur=dummyHead->next;
            //cout<<"size:"<<m_size<<" index:"<<index<<endl;
            //cout<<cur->val<<endl;
            //cout<<cur->next->val<<endl;
            while(index--)
            {
                cur=cur->next;
              //  cout<<cur->val<<endl;
            }
            cout<<cur->val<<endl;
            return cur->val;//判断过index的范围在链表size的有效范围,因此cur必定有值,无需再判是否为空指针。
        }
        
        void addAtHead(int val) {
            //先创建一个新节点
            ListNode *newNode=new ListNode(val);
            newNode->next=dummyHead->next;//newnode相当于插入在dummyhead和第一个节点之间
            dummyHead->next=newNode;//注意,将新加的节点放在虚拟节点的后面
            m_size+=1;
        }
        
        void addAtTail(int val) {
            //先创建一个新节点
            ListNode*newNode=new ListNode(val);
            ListNode *cur=dummyHead;
            //找到链表尾节点
            while(cur->next!=nullptr){
                cur=cur->next;
            }
            //将新节点串到尾节点上
            cur->next=newNode;
            //更新链表大小
            m_size+=1;
        }
        
        void addAtIndex(int index, int val) {
            ListNode *newNode=new ListNode(val);
            ListNode *cur=dummyHead;
            if(index>m_size)//若index大于size,直接返回
            {
                return;
            }
            //找到第index个节点的前一个节点
            while(index--)
            {
                cur=cur->next;
            }
            //令cur下一个节点指向新节点,同时新节点的下一个节点指向cur原始的下个节点
            // ListNode *tmp=cur->next;//若index=size,该值为空指针,也满足。
            // cur->next=newNode;
            // newNode->next=tmp;
            newNode->next=cur->next;
            cur->next=newNode;
           // cout<<"增加后的链表"<<dummyHead->next->val<<cur->val<<cur->next->val<<endl;
            //更新链表size
            m_size+=1;
        }
        
        void deleteAtIndex(int index) {
            if(index>=m_size||index<0)//注意可以取到等号
            {
                return;
            }
            ListNode *cur=dummyHead;//当前节点先指向头节点
            //cout<<cur->next->val<<endl;
            //找到index的位置
            while(index--)
            {
                cout<<cur->next->val<<endl;
                cur=cur->next;
            }
            //cur指向index前一个元素
            ListNode*tmp=cur->next;
            cur->next=cur->next->next;
            delete tmp;
            //更新size
            m_size=m_size-1;
            //cout<<m_size+10<<endl;
        }
        private:
        int m_size;
        ListNode *dummyHead;
    };
    
    

    4)总结

    链表定义时初始化了一个虚拟头节点,数据为0,这个节点不计入index中。返回链表index个元素的时候下标从0开始,从第一个有效节点开始算,虚拟头节点不算。index与数组下标类似。

    在第index位置插入元素时,需要把当前节点(从虚拟节点开始)先移动到index的前一个节点,移动次数为index次,数不清次数时可以画图。

    设计到链表index时,一定要先判断index是否在有效范围内,与0,size之间的关系是否有效,若无效直接返回。

    多个函数调试出错时可以通过打日志的方式记录,判断到底是执行哪个函数时出错,哪个循环内出问题。

  4. 反转链表

1)题目描述

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

img

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

2)解题思路

单链表反转的本质是链表节点的插入。输出链表初始只有虚拟节点和头节点,将头节点后面的节点(作为当前节点)依次插入虚拟节点和新头节点之间,即可完成链表的反转。

关键在于每次插入完需要更新头节点和当前节点到底是哪一个。

3)代码实现

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==nullptr)//若链表为空,返回
        {
            cout<<0<<endl;
            return 0;

        }
        //创建一个虚拟头节点
        ListNode* dummyHead=new ListNode(0);//创建虚拟头节点需要在堆区创建
        ListNode*cur=head;
        
        //将第一个节点链接在虚拟头节点后面
        dummyHead->next=head;
        //将cur->next节点插入dummyHead和head之间
        while(cur->next!=nullptr)
        {      
        //在虚拟头节点和头节点之间插入第二个节点
            ListNode *tmp=cur->next->next;
            //cout<<"q下一个节点值"<<q->next->val<<endl;
            dummyHead->next=cur->next;
            cur->next->next=head;     
        //完成插入后更新head节点和cur->next节点
            cur->next=tmp;//将cur->next移到tmp处
            head=dummyHead->next;//将head移到更新的head处
           // cout <<dummyHead->next->val<<endl;
        }
        return head;
    }
};

4)总结

1.链表一定要定义好head节点,dummyHead节点,cur节点究竟指向哪一个节点,否则在循环中容易绕晕

2.创建虚拟头节点时需要在堆区new出来,不能直接调用有参构造函数创建,否则会报错。下图写法错误,错误原因不是很清楚。

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

//这种创建节点方法错误,错误原因是啥??
ListNode* dummyHead(0);//这是个匿名对象吗??

//这种写法编译器不报错
auto p=ListNode(5);

//正确的写法
ListNode* dummyHead=new ListNode(0);
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) {}

};

//这种创建节点方法错误,错误原因是啥??
ListNode* dummyHead(0);//这是个匿名对象吗??

//这种写法编译器不报错
auto p=ListNode(5);

//正确的写法
ListNode* dummyHead=new ListNode(0);


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值