02.Leetcode刷题:链表专题

一.移除链表元素

题意:删除链表中等于给定值 val 的所有节点。
示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2: 输入:head = [], val = 1 输出:[]
示例 3: 输入:head = [7,7,7,7], val = 7 输出:[]

解题思路:
解法一:不使用虚拟头节点
此时需要考虑两种情况:第一种情况是删除的元素就是头节点,第二种情况是删除的元素不是头节点。
删除某个元素,先要找到他的前一个元素,然后将前一个元素的next指向要删除元素的下一个节点。
解法二:使用虚拟头节点
创建一个虚拟头节点,使得虚拟头节点的next指向原先的head,删除元素的方法与上面相似。删除完之后,让head指向虚拟头节点的next,释放虚拟头节点即可。

解法一代码:

/**
 * 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) { // 注意这里不是if
            ListNode* tmp = head;
            head = head->next;
            delete tmp;
        }

        // 删除非头结点
        ListNode* cur = head;
        while (cur != NULL && cur->next!= NULL) {
            if (cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        return head;
    }
};

解法二代码:

/**
 * 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* dummyHead = new ListNode(0);//创建一个虚拟头节点
        dummyHead->next = head;// 将虚拟头结点指向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;
    }
};

二.反转链表

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

解题思路:
方法一:双指针法
使用两个指针,定义第一个指针为cur,指向链表的头节点,由于我们是反转操作。头节点最后会变成尾节点,头节点最后会指向NULL,因此定义一个pre节点为NULL。根据下图,我们可以看出cur和pre节点会不断的向右移动,cur->next会变成pre完成链表反转的操作。然后将cur和pre一起向右移动。当pre移动到最后一个节点的时候,cur节点指向为空。因此while循环的判断条件为cur指向为空时,结束循环。反转完毕以后,pre为头节点,将其返回即可。
在这里插入图片描述

/**
 * 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* cur = head;
        ListNode* pre = NULL;
        ListNode* temp = NULL;
        while (cur)//cur=NULL时,pre指向尾元素,此时结束循环
        {
            temp = cur->next; //保存的是未反转链表的下一个节点
            cur->next = pre;//反转操作,将当前节点的next指向前一个结点
            pre = cur;//将pre和cur右移
            cur = temp;
        }
        return pre;
    }
};

解法二:递归法

/**
 * 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 == NULL) return pre;
        ListNode* temp = cur->next;//下一个cur的位置
        cur->next = pre;//改变指向
        return reverse(temp,cur);//将temp赋值给原来的cur,将cur赋值给原来的pre;cur变成了下一个cur,pre变成原来的cur
    }

    ListNode* reverseList(ListNode* head) {
        return reverse(head, NULL);
    }
};

三.两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
在这里插入图片描述
解题思路:
按题目的意思,是将相邻的两个元素进行交换,从第一和第二两个元素交换开始,我们发现,需要第三个节点的参与才可以完成交换,因此考虑引入一个虚拟头节点。引入一个虚拟头节点后,按照下图所示总计分为三个步骤:第一步是将虚拟头节点的next指向第二个节点,第二步是将第二个节点的next指向第一个节点(此处第一与第二个节点发生了位置交换),第三步是将原来第一个节点的next指向第三个节点,至此,前两个节点的顺序发生转换。
在这里插入图片描述
操作后的链表顺序如下:
在这里插入图片描述
在这里插入图片描述

/**
 * 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* swapPairs(ListNode* head) {
        ListNode *dummyHead = new ListNode(0);//设置一个虚拟头节点
        ListNode* cur = dummyHead;
        dummyHead->next = head;
        while (cur->next != NULL && cur->next->next !=NULL)/*第一个条件限制偶数个元素,第二个条件限制奇数个元素的链表。
        													两个判断条件的顺序不能改变,把第二个判断条件放到前面可能会造成空指针操作*/
        {
            ListNode*temp1 = cur->next;
            ListNode* temp2 = cur->next->next->next;

            cur->next = cur->next->next;//步骤一
            cur->next->next = temp1;//步骤二
            cur->next->next->next = temp2;//步骤三

            cur = cur->next->next;// cur移动两位,准备下一轮交换// cur移动两位,准备下一轮交换
        }
        return dummyHead->next;
    }
};

四.删除链表倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
在这里插入图片描述
解题思路:
我们要删的是倒数第n个节点,所以我们**要先找到倒数第n+1个节点,才能删除第n个节点。**采用双指针的方式进行操作,先让快指针走n+1步,然后快慢指针同时移动,直到快指针指向了末尾NULL,然后删除慢指针所指向的下一个节点,完成。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

/**
 * 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* removeNthFromEnd(ListNode* head, int n) {
        ListNode* buddyHead = new ListNode(0);
        ListNode* fastIndex = buddyHead;
        ListNode* slowIndex = buddyHead;
        buddyHead->next = head;
        n += 1;//防止n超过链表长度,导致对空指针操作
        while (n-- && fastIndex!=NULL)
        {
            fastIndex = fastIndex->next;
        }
        
        while(fastIndex != NULL)
        {
            fastIndex = fastIndex->next;
            slowIndex = slowIndex->next;
        }
        slowIndex->next = slowIndex->next->next;
        return buddyHead->next;
    }
};

五.环形链表

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
在这里插入图片描述
解题思路:
首先第一点:需要判断链表是否有环。采用双指针法,定义快慢指针slow与fast。fast比slow走的快,如果有环,那么fast与slow有可能相遇。定义快指针一次走两步,慢指针一次走一步。
第二点:是找到这个环的入口。假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。
在这里插入图片描述
当slow与fast相遇的时候,slow一共走了x+y,fast一共走了x+y+n*(z+y);fast可能已经在环里面走了好几圈才遇上slow,n为fast在环内所走的圈数。
因为fast一次走两步,slow一次走一步,因此fast的路程一定是slow路程的两倍,因此可以得到这样的等式:2*(x+y)=x+y+n*(z+y),进一步化简x+y=n*(z+y);fast让一圈之后x=(n-1)(y+z)+y+z-y;
最后得到x=(n-1)(y+z)+z;当n=1时,x=z;当n大于1时,也是一样的结果,只是fats在环里面多转了好几圈罢了,x与z的值始终相等
这个时候分别定义两个指针:一个指向head,一个指向fast与slow的相遇点,两个指针同时移动,每次移动一步,当两个指针相遇的时候,此时的下标就是环的入口。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;

        while (fast != NULL && fast->next != NULL)
        {
            fast = fast->next->next;
            slow = slow->next;
            if (slow == fast)
            {
                ListNode* indexFast = fast;
                ListNode* indexSlow = head; 
                while(indexFast != indexSlow)
                {
                   indexFast = indexFast->next;
                   indexSlow = indexSlow->next; 
                }
                return indexFast;
            }
        }
        return NULL;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值