LeetCode从零开始 链表篇(待细化)

首先我们来了解C++中如何对链表进行定义:

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

抛去方法不谈 我们发现链表的定义主要有两部分,第一部分为节点数值 val ,第二部分为节点指针 next
如何声明一个新的链表呢?

ListNode* cur=new ListNode(0);

利用第二个函数,当我们没有定义指针指向时,默认指向为空。
当然通过第三个函数,我们可以对指针指向进行声明。
LeetCode 203 移除链表元素
在这里插入图片描述
此题其实较为简单,适合入门学者练手,我们只需注意一个问题即可:
如果要删除的元素恰好在链表的头结点,该如何处理?

众所周知链表的删除是利用前一节点进行指针的断开实现删除,那么头结点该怎么删除呢?
有两种方法,一种是直接将头结点后移,并删除原节点,但这种情况需要单独分离讨论,略显复杂。

ListNode* temp=head;
head=head->next;
delete temp;

第二种办法,建立一个虚拟的头结点,使当前头结点变为正常节点。

ListNode* dummyHead =new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;

后进行正常的删除即可。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
            //构造虚拟头结点
            ListNode*  dummyHead= new ListNode(0);
            //将虚拟头结点指向原头结点
            dummyHead->next = head;
            //构造临时链表指向虚拟头结点
            ListNode* cur = dummyHead;
            while(cur->next != NULL){
            	//检测下一个节点值是否需要删除
                if(cur->next->val == val){
                	//保存要删除的节点,以便后续清除内存
                    ListNode* temp=cur->next;
                    //指针指向下下个节点
                    cur->next=cur->next->next;
                    //清除要删除节点内存
                    delete temp;
                }
                else{
                    //如不需要删除,则向后移动
                    cur=cur->next;
                }
            }
            //移交头结点
            head = dummyHead->next;
            //清空虚拟头结点内存
            delete dummyHead;
            return head;    
    }
};

接下来的这道题能很好的考察链表的基本操作。
在这里插入图片描述

class MyLinkedList {
public:
    struct LinkedList {
        int val;
        LinkedList* next;
        LinkedList(int val):val(val),next(nullptr){};
    };
    MyLinkedList() {
        dummyHead = new LinkedList(0);
    }
    
    int get(int index) {
        //首先判断index值是否违法
        if(index < 0){
            return -1;
        }
        //指向虚拟头结点
        LinkedList* cur = dummyHead;
        //注意index从0开始
        for(int i = 0;i <= index;i++){
            //如果遍历到末尾 说明index超限
            if(cur->next == NULL){
                return -1;
            }
            //合法则后移
            cur = cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
    	//赋值给新节点
        LinkedList* cur = new LinkedList(val);
        //新节点指向头结点
        cur->next = dummyHead->next;
        //虚拟头结点左移
        dummyHead->next = cur;
    }
    
    void addAtTail(int val) {
        LinkedList* cur = dummyHead;
        LinkedList* temp = new LinkedList(val);
        while(cur->next != NULL){
            cur = cur->next;
        }
        cur->next = temp;
    }
    
    void addAtIndex(int index, int val) {
        if(index < 0){
            index=0;
        }
        LinkedList* cur = dummyHead;
        for(int i = 0;i < index;i++){
            // 移动前先判断
            if(cur->next == NULL){
                return;
            }
            cur = cur->next;
        }
        LinkedList* temp = new LinkedList(val);
        temp->next = cur->next;
        cur->next = temp;
    }
    
    void deleteAtIndex(int index) {
        if(index < 0){return;}
        LinkedList* cur = dummyHead;
        for(int i = 0;i < index;i++){
            if(cur->next == NULL){
                return;
            }
            cur = cur->next;
        }
        LinkedList* temp = cur->next;
        //这里记得要再判断一次 因为最后一个元素后面并没有元素可以删
        if(cur->next == NULL){
           return;
        }
        else{ cur->next = cur->next->next;}
        delete temp;
    }
    private: 
    LinkedList* dummyHead;
};

进阶之双指针:
在这里插入图片描述
链表逆序,看似远离简单,但如果方法不对,很容易陷入死循环。

抛开实际操作,逆序链表我们首先想到的办法就是:从第二个节点开始,让他指向前一个节点,不断循环。
那我们需要记住两个位置,一个是下一个要反指的节点,另一个是要被指向的节点。因此不难想到用两个指针保存它们的位置。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;
        ListNode* slow = NULL;
        ListNode* fast = cur;
        ListNode* temp = new ListNode();
        while(fast != NULL){
        	//如果fast移动至尾结点,只需反指即可,无需继续跳跃
            if(fast->next == NULL){
                fast->next = slow;
                return fast;
            }
            //临时节点保存下一次fast节点要跳跃的位置
            temp = fast->next;
            //反指
            fast->next = slow;
            //移动slow指针
            slow = fast;
            //fast向后跳跃
            fast = temp;
        } 
        return fast;
    }
};

笔者起初想法是定义两个指针: slow 、fast
slow指向头结点,fast指向头结点的下个节点。但这样在运行时报出了heap-use-after-free的错误,让笔者两眼一黑。
一个完全不理解的错误,在查询了多方资料后,发现核心是 对链表尾结点指向问题。
当尾结点没有指向时,计算机默认该链表并未建立完毕
所以笔者重新定义了 slow 和 fast 指针的初始位置,使slow指向null,fast指向头结点,这样在逆序的时候便自动完成了逆序后尾结点的指向问题。
另外注意fast移动至末尾时,无需再移动slow与跳跃fast。

在这里插入图片描述

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* temp = new ListNode();
        ListNode* middle = new ListNode();
        middle->next=head; 
        ListNode* slow = head;
        ListNode* cur = middle;
        if(head == NULL || head->next == NULL){
            return head;
        }
        ListNode* fast = head->next;
        while(fast != NULL){
            temp=fast->next;
            fast->next = slow;
            slow->next = temp;
            middle->next = fast;
            middle = slow;
            slow = temp;
            if(temp==NULL){
                break;
            }
            fast=temp->next;
        }
        cur=cur->next;
        return cur;
    }
};

在这里插入图片描述

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* slow=head;
        ListNode* fast=head;
        ListNode* cur=head;
        if(head->next == NULL){
            return NULL;
        }
        for(int i = 0;i<n;i++){
            fast = fast->next;
        }
        if(fast == NULL){
            head = head->next;
            return head;
        }
        while(fast->next != NULL){
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return cur;
    }
};

在这里插入图片描述

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int i=0,j=0;
        //建立临时节点用来计算链表长度
        ListNode* cur=headA;
        ListNode* cur1=headB;
        while(cur!=NULL){
            cur=cur->next;
            i++;
        }
        while(cur1!=NULL){
            cur1=cur1->next;
            j++;
        }
        ListNode* a=headA;
        ListNode* b=headB;
        if(i>j){
            int length=i-j;
            while(length>0){
                a=a->next;
                length--;
            }
        }
        else{
            int length=j-i;
            while(length>0){
                b=b->next;
                length--;
            }
        }
        while(a!=b){
                a=a->next;
                b=b->next;
            }
            return a;
    }
};

此题笔者最初犯了个错误,在遍历链表求长度时,使用原节点a,b进行遍历,导致最终ab均指向尾结点,在后面的操作涉及向后移动时便报出空指针异常,特纪录在此提醒自己,应设立临时节点指向a,b进行遍历,以免影响原节点位置。

在这里插入图片描述

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast != NULL && fast->next != NULL){
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast){
                ListNode* index1 = fast;
                ListNode* temp = head;
                while(temp != index1){
                    temp = temp->next;
                    index1 = index1->next;
                }
                return index1;
            }
        } 
        return NULL;
    }       
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值