链表算法题

目录

链表算法题的常用技巧

题目一——206. 反转链表 - 力扣(LeetCode)

题目二——876. 链表的中间结点 - 力扣(LeetCode) 

题目三——输出单向链表中倒数第k个结点_牛客题霸_牛客网 

题目四——203. 移除链表元素 - 力扣(LeetCode) 

题目五——21. 合并两个有序链表 - 力扣(LeetCode) 

题目六——链表分割_牛客题霸_牛客网

题目七——链表的回文结构_牛客题霸_牛客网 

题目八——160. 相交链表 - 力扣(LeetCode)

题目九——141. 环形链表 - 力扣(LeetCode) 

题目十——142. 环形链表 II - 力扣(LeetCode) 

题目十一——138. 随机链表的复制 - 力扣(LeetCode)

题目十二—— 2. 两数相加 - 力扣(LeetCode)

题目十三——24. 两两交换链表中的节点 - 力扣(LeetCode)

题目十四——143. 重排链表 - 力扣(LeetCode) 

题目十五——23. 合并 K 个升序链表 - 力扣(LeetCode) 

15.1.暴力解法

15.2.分治—递归解法

15.3.优先级队列解法

题目十六——25. K 个一组翻转链表 - 力扣(LeetCode)


链表算法题的常用技巧

1.画图:画图是最实用的

2.引入虚拟头节点:我们的算法题都是直接给你一个没有头节点的链表的,我们可以加上头节点,使得我们的算法过程更简单

3.不要吝啬空间,大胆去定义变量

我们来仔细讲一个例子;

在下面这个链表里面插入一个元素,我们通常就是按照下面这个图这么做的,而且具有强烈的先后顺序

但其实我们可以先定义一个next指针,让next 指针指向后面那个元素

这个时候我管你语句的执行顺序是什么,都是可以的。

4.快慢双指针

这个常常用于下面3种情况

  1. 判环
  2. 找链表中环的入口
  3. 找链表中倒数第n个结点

 5.链表中的常用操作

  • 1.创建一个新结点
  • 2.尾插
  • 3.头插(逆序链表题目等)

题目一——206. 反转链表 - 力扣(LeetCode)

 

想必这题,大家已经知道是使用前插的思想来解决的了。

如果大家不懂:可以去链表OJ1——反转链表(c语言实现)-优快云博客

那么我们很快就能写出下面这个代码

class Solution {
public:
    struct ListNode* reverseList(struct ListNode* head) 
    {  
    // 如果链表为空或只有一个节点,则不需要反转,直接返回  
    if (head == NULL || head->next == NULL)  
        return head;  
  
    struct ListNode* next = head; // 初始化next指针,用于遍历原链表  
    head = NULL; // 新的头节点初始化为NULL,因为反转后原来的头节点会变成尾节点  
  
    // 遍历原链表的每个节点  
    while (next != NULL) {  
        struct ListNode* cur = next; // 当前节点  
        next = next->next; // 将next指针指向下一个节点,以便在循环中继续遍历  
  
        // 将当前节点的next指针指向新的头节点,实现链表的反转  
        cur->next = head;  
  
        // 更新头节点为当前节点,因为当前节点现在是新的头节点  
        head = cur;  
    }  
  
    // 返回反转后的链表的头节点  
    return head;  
    }
};

 

题目二——876. 链表的中间结点 - 力扣(LeetCode) 

 

暴力解法我放在了:链表OJ2——链表的中间结点(c语言实现)-优快云博客 

既然我们要找的是中间位置的结点,那么我们可以定义两个指针,第一个指针指向当前遍历到的最后一个结点,第二个指针时刻指向已经遍历过的结点的中间结点。如此进行下去,因为第二个指针始终指向的是已经遍历过的结点的中间结点,所以当链表遍历完后直接返回第二个指针即可。这就是所谓的“快慢指针”。

我们不妨定义两个指针名叫:fast,slow。

  1. fast:记录当前遍历到的最后一个结点。(快指针)
  2. slow:记录已经遍历过的结点的中间结点。(慢指针)

通过观察,我们可以发现,当slow指针走一步时,fast指针走一步或是走两步都满足slow指针指向的是已经遍历过的结点的中间结点。也就是slow指针走一步,fast指针最多可以走两步。

所以,我们可以遍历链表,当fast指针遍历到链表末尾时,就立刻返回此时的slow指针即可。

需要注意的是:因为fast指针一次是走两步,所以当fast指针指向的内容为空或是fast指针指向的结点所指向的内容为空时,均停止遍历链表。

 这两种情况下均停止遍历,立刻返回slow指针。

这样,我们就在只遍历了一遍链表的情况下找到了中间结点的位置,即时间复杂度为O(n)。

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        // 定义两个指针,fast和slow,都从链表的头节点开始
        ListNode* fast = head, *slow = head;
        
        // 使用快慢指针技巧来找到链表的中间节点
        // 当快指针fast没有到达链表末尾,并且快指针的下一个节点也不为NULL时,循环继续
        while (fast != NULL && fast->next != NULL) {
            // 慢指针slow每次向前移动一步
            slow = slow->next;
            // 快指针fast每次向前移动两步
            fast = fast->next->next;
        }
        
        // 当循环结束时,慢指针slow指向的节点即为链表的中间节点
        // 这是因为快指针fast的速度是慢指针slow的两倍,所以当快指针到达链表末尾时,慢指针恰好在链表的中间
        return slow;
    }
};

 

题目三——输出单向链表中倒数第k个结点_牛客题霸_牛客网 

 

如果想看暴力解法的去: 链表OJ3——链表中倒数第k个结点-优快云博客

        其实我们还是可以运用“快慢指针”的思想来解决这道题,这样可以使得代码的时间复杂度直接从O(n2)变为O(n) 。需要注意的是:这里所说的“快慢指针”并非一个指针走得快,另一个指针走得慢,而是快指针先走,慢指针在快指针走到某一位置后再开始走。

  • 因为从最后一个结点开始,再往后走一步便是NULL;
  • 从倒数第二个结点开始,再往后走两步便是NULL;
  • 从倒数第k个结点开始,再往后走k步便是NULL。

所以我们可以先让快指针(fast)先走k步,然后慢指针(slow)再和快指针一起往后走,这样,当快指针走到NULL时,慢指针指向的结点就是倒数第k个结点。

我在这里先放一个模板(跟本题目没什么关系啊):

struct ListNode {
	int val;
	struct ListNode *next;
};
 
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k)
{
	struct ListNode* fast = pListHead;//快指针
	struct ListNode* slow = pListHead;//慢指针
	while (k--)//快指针先向后移动k步
	{
		if (fast == NULL)//快指针移动过程中链表遍历结束,不存在倒数第k个结点
			return NULL;
		fast = fast->next;//快指针后移
	}
	while (fast)//快指针遍历完链表时结束遍历
	{
		fast = fast->next;//快指针后移
		slow = slow->next;//慢指针后移
	}
	return slow;//返回慢指针的值
}

 接着我们就来看看这个题目怎么做

有没有发现这道题目说是链表的倒数第K个元素,但是我们真的有必要去创建链表吗?没必要啊,我们完全就可以创建一个数组,找数组的倒数第K个元素。  

#include <iostream>
#include<vector>
using namespace std;

int main() {
    int n;
    while (cin>>n) { // 注意 while 处理多个 case
        vector<int> arr(n);//我们使用数组来充当链表
        for(int x=0;x<n;x++)
        {
            cin>>arr[x];
        }
        int k;
        cin>>k;
        int fast=0;//快指针
        while(k--&&fast<n)//快指针先走k步
        {
            fast++;
        }
        int slow=0;//慢指针
        while(fast<n)//快慢指针一起走
        {
            slow++;
            fast++;
        }
        cout<<arr[slow]<<endl;//此时慢指针指向的数就是中间结点
    }
}

 

题目四——203. 移除链表元素 - 力扣(LeetCode) 

 

这道题就是想考验我们删除链表元素的操作,我们可以使用三指针方法来解决,但是三指针方法有它的缺点——头结点需要特殊处理,我这里就不讲了,感兴趣的可以去:链表OJ4——删除链表中等于给定值 val 的所有节点(c语言实现)-优快云博客

 我这里采用添加头结点的解法,这样子就不需要特殊处理了

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //创建头结点
        ListNode*guard=new ListNode();
        guard->next = head;//让头结点指向链表的第一个结点
	    
        ListNode* cur = guard->next;//cur指针指向原链表第一个结点
	    ListNode* prev = guard;//prev指针指向头结点

        while (cur != NULL)//当cur为空时,循环停止
	    {
	    	if (cur->val == val)//当前排查的结点是待移除的结点
	    	{
	    		ListNode* next = cur->next;//记录待排查结点的后一个结点位置
	    		prev->next = next;//prev指针指向的结点指向next
	    		delete cur;//将cur指针指向的结点释放掉
	    		cur = next;//将next指针赋值给cur指针
	    	}
	    	else//当前排查的结点不是待移除的结点
	    	{
	    		prev = cur;//指针后移
	    		cur = cur->next;//指针后移
	    	}
	    }
	    head = guard->next;//将头结点指向的位置赋值给头指针,使头指针指向链表第一个结点
	    delete guard;//释放头结点
	    guard = NULL;//及时置空
	    return head;//返回新的头指针
    }

};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值