代码随想录——链表

目录

一、链表的理论知识

二、设计链表

三、交换链表节点

二、快慢指针

三、链表相交或成环


一、链表的理论知识

链表节点的定义

struct ListNode{
    ListNode*next;
    int val;
    ListNode():val(0),next(nullptr){}
    ListNode(int v):val(v),next(nullptr){}
    ListNode(int v,ListNode*ptr):val(v),next(ptr){}
}

每个链表节点结构都包含一个next指针,一个val节点值。并且含有构造函数,结构体中的构造函数,可以直接将结构体成员变量当作private变量进行直接赋值使用。

二、设计链表

1、要设计一个链表类首先应该考虑的就是设计一个链表节点,以及节点的构造函数
2、一个链表类中为了方便对链表进行操作,应该定义一个链表头节点变量,方便后续对该连表类的其它成员方法函数进行操作时给定操作基础。
3、在链表类的所有方法函数中,实际操作都不能对定义的链表头节点进行直接操作。因为链表头节点作为private变量,被改动后会保持当前状态,当多个成员方法函数切换使用时,此时头节点变量会混乱。因此各方法函数在对头节点变量进行访问时,需要copy一个临时头节点再进行操作。

注意:在构建链表类时,题目中的 MyLinkedList linkedList = new MyLinkedList();操作是一个内存分配操作,此时并没有构建节点,所以头节点初始化ListNode*head=nullptr。

class MyLinkedList {
private:
    struct listNode{
        listNode*next;
        int val;
        listNode(int value,listNode*ptr)
        {
            next=ptr;
            val=value;
        }
    };
    listNode*head=nullptr;
public:
    MyLinkedList() {
    //不需要构建节点
    //head=new listNode(0,nullptr);
    }
    
    int get(int index) {
    listNode* temp=head;
    for(int i=0;i<index;i++)
    {
        temp=temp->next;
    }
    return temp->val;
    }
    
    void addAtHead(int val) {
    listNode*node=new listNode(val,nullptr);
    node->next=head;
    head=node;
    }
    
    void addAtTail(int val) {
    listNode* temp=head;  
    while(temp->next!=nullptr)
    {
        temp=temp->next;
    }
    listNode*node=new listNode(val,nullptr);
    temp->next=node;
    }
    
    void addAtIndex(int index, int val) {
    listNode* temp=head;  
    for(int i=0;i<index-1;i++)
    {
        temp=temp->next;
    }
    listNode*node=new listNode(val,nullptr);
    listNode* temp1=temp->next;
    node->next=temp1;
    temp->next=node;
    }
    
    void deleteAtIndex(int index) {
    listNode* temp=head;  
    for(int i=0;i<index-1;i++)
    {
        temp=temp->next;
    }   
    temp->next=temp->next->next;
    }

};

三、交换链表节点

1、交换链表节点

class Solution {
public:
    ListNode* swapPairs(ListNode* root) {
    ListNode* cur=new ListNode(0,root);
    ListNode* reuslt=cur;
    while(root!=nullptr&&root->next!=nullptr)
    {
        ListNode*temp=root->next;//保存中间节点temp
        root->next=temp->next;//步骤1
        temp->next=root;//步骤2
        cur->next=temp;//步骤3
        //更新cur节点、root节点,temp节点在上面可以根据root节点获取
        cur=cur->next->next;
        root=cur->next;
    }
    return reuslt->next;
    }
};

2、重排链表

将链表节点都存进数组里面,然后遍历数组进行链表重排。

class Solution {
public:
    void reorderList(ListNode* head) {
    //第一步:先将所有链表节点都存进数组里
    vector<ListNode*> node;
    while(head!=nullptr)
    {
        node.push_back(head);
        head=head->next;
    }
    ListNode* cur=new ListNode(0);
    ListNode* result=cur;
    //第二步:遍历数组首尾,重排链表
    int start=0;
    int end=node.size()-1;
    while(end>start)
    {
        cur->next=node[start];
        start++;
        cur=cur->next;
        cur->next=node[end];
        end--;
        cur=cur->next;
    }
    //奇数个节点时会出现的情况
    if(start==end)
    {
        cur->next=node[end];
        cur=cur->next;
    }
    //给链表加上尾节点
    cur->next=nullptr;
    head=result->next;
    
    }
};

3、反转链表

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
    ListNode* cur=new ListNode(0);
    while(head!=nullptr)
    {
        ListNode*temp=head->next;//保存原链表头节点的next节点
        //将原链表头节点从原链表中剥离出来,放在cur->....->cur->next之间
        head->next=cur->next;
        cur->next=head;
        //更新原链表的新的头节点
        head=temp;
    }
    return cur->next;
    }
};

二、快慢指针

1、回文链表

方法一是使用数组将链表各节点的值保存到数组中,再对数组判断回文;

方法二是将链表的后半段反转作为新的链表,和链表的前半段挨个比较节点值;

本次主要介绍方法二,加深快慢指针的使用印象,分为如下几步:

  • 用快慢指针,快指针有两步,慢指针走一步,快指针遇到终止位置时,慢指针就在链表中间位置
  • 同时用pre记录慢指针指向节点的前一个节点,用来分割链表
  • 将链表分为前后均等两部分,如果链表长度是奇数,那么后半部分多一个节点
  • 将后半部分反转 ,得cur2,前半部分为cur1
  • 按照cur1的长度,一次比较cur1和cur2的节点数值
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (head == nullptr || head->next == nullptr) return true;
        ListNode* slow = head; // 慢指针,找到链表中间分位置,作为分割
        ListNode* fast = head;
        ListNode* pre = head; // 记录慢指针的前一个节点,用来分割链表
        while (fast && fast->next) {
            pre = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        pre->next = nullptr; // 分割链表
        ListNode* cur1 = head;  // 前半部分
        ListNode* cur2 = reverseList(slow); // 反转后半部分,总链表长度如果是奇数,cur2比cur1多一个节点
        // 开始两个链表的比较
        while (cur1) {
            if (cur1->val != cur2->val) return false;
            cur1 = cur1->next;
            cur2 = cur2->next;
        }
        return true;
    }
    // 反转链表
    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* cur = head;
        ListNode* pre = nullptr;
        while(cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

2、删除链表的倒数第N个节点

思路一:可以先第一次遍历链表,将每个节点都保存在一个数组元素中,然后再进行删除操作

思路二:双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

千万注意:使用C/C++编程语言,移除链表节点后,千万不要忘记删除这个移除的节点!!!

 //如果是题目中出现,删除第index个节点,可以直接链表从头到尾遍历过去,计数达到后删除
 //如果是题目中出现,删除倒数第index个节点:
 //思路一:可以先第一次遍历链表,将每个节点都保存在一个数组元素中,然后再进行删除操作
 //思路二:双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    vector<ListNode*> array(30);
    int i=0;
    ListNode*result=new ListNode(0,head);
    while(head!=nullptr)
    {
      array[i++]=head;
      head=head->next;
    }
    int num=i-n+1;
    if(num==1)//情况一:删除的是第一个节点
    result=result->next;
    else
    array[num-2]->next=array[num-1]->next;//情况二:删除的不是第一个节点
    return result->next;
    }
};

三、链表相交或成环

1、链表相交

//看到链表问题,并且涉及到倒数N节点的判断时,都可想到数组
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    vector<ListNode *>arrayA(30000);
    vector<ListNode *>arrayB(30000);
    int i=0;
    int j=0;
    while(headA!=nullptr) 
    {
        arrayA[i++]=headA;
        headA=headA->next;
    }
    while(headB!=nullptr) 
    {
        arrayB[j++]=headB;
        headB=headB->next;
    }
    int tempA=i-1;
    int tempB=j-1;
    while(tempA>=0&&tempB>=0&&arrayA[tempA]==arrayB[tempB])
    {
        tempA--;
        tempB--;
    }
    if(tempA==i-1)
    return NULL;
    else
    return arrayA[tempA+1];

    }
};

2、环形链表

 除哈希表算法,还有“龟兔赛跑算法”

class Solution {
public:
    bool hasCycle(ListNode *head) {
    unordered_set<ListNode*> mat;
    while(head!=nullptr)
    {
        if(mat.find(head)!=mat.end())
        return true;
        else
        {
            mat.insert(head);
            head=head->next;
        }
    }
    return false;   
    }
};

 3、环形链表||

方法一:哈希表

一个非常直观的思路是:我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode *> visited;
        while (head != nullptr) {
            if (visited.count(head)) {
                return head;
            }
            visited.insert(head);
            head = head->next;
        }
        return nullptr;
    }
};

方法二:快慢指针

我们使用两个指针,fast与 slow。它们起始都位于链表的头部。随后,slow 指针每次向后移动一个位置,而 fast指针向后移动两个位置。如果链表中存在环,则 fast指针最终将再次与 slow指针在环中相遇。

因此,当发现 slow与 fast相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow每次向后移动一个位置。最终,它们会在入环点相遇。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *slow = head, *fast = head;
        while (fast != nullptr) {
            slow = slow->next;
            if (fast->next == nullptr) {
                return nullptr;
            }
            fast = fast->next->next;
            if (fast == slow) {
                ListNode *ptr = head;
                while (ptr != slow) {
                    ptr = ptr->next;
                    slow = slow->next;
                }
                return ptr;
            }
        }
        return nullptr;
    }
};

参考代码随想录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值