链表学会了没,会做这些题就足够了,思路全在动图里了,不懂都难《上篇》
Posted 跳动的bit
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表学会了没,会做这些题就足够了,思路全在动图里了,不懂都难《上篇》相关的知识,希望对你有一定的参考价值。
1.移除链表元素 <难度系数⭐>
📝 题述:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == 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
输出:[ ]
🧷 平台:Visual studio 2017 && windows
🔑 核心思想:
思路1,双指针,cur找到val值所在的节点,prev记录前一个位置,依次删除,细节请看下图:
思路2,新链表,把链表中所有不是val的值尾插至新链表,删除val的节点
#include<stdio.h>
#include<stdlib.h>
typedef int SLTDataType;
struct ListNode
{
int val;
struct ListNode *next;
};
//思路1
struct ListNode* removeElements1(struct ListNode* head, int val)
{
struct ListNode * cur = head;
struct ListNode * prev = NULL;
while (cur)//判断地址
{
//1、相等的情况
if (cur->val == val)
{
if (cur == head)//1.1、第1个节点等于val时
{
head = head->next;
free(cur);
cur = head;
}
else//1.2、其它节点等于val时
{
prev->next = cur->next;
free(cur);
cur = prev->next;
}
}
//2、不相等的情况
else
{
prev = cur;
cur = cur->next;
}
}
return head;
}
//思路2
struct ListNode* removeElements2(struct ListNode* head, int val)
{
if(head == NULL)
return NULL;
//创建新节点
struct ListNode* newhead = NULL, *tail = NULL;
struct ListNode* cur = head;
while(cur)
{
struct ListNode* next = cur->next;
if(cur->val == val)
{
free(cur);
}
else
{
if(tail == NULL)
{
newhead = tail = cur;
}
else
{
tail->next = cur;
tail = cur;
}
}
cur = next;
}
if(tail)
tail->next = NULL;
return newhead;
}
int main()
{
struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
n1->val = 1;
struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
n2->val = 2;
struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
n3->val = 6;
struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
n4->val = 3;
struct ListNode* n5 = (struct ListNode*)malloc(sizeof(struct ListNode));
n5->val = 4;
struct ListNode* n6 = (struct ListNode*)malloc(sizeof(struct ListNode));
n6->val = 5;
struct ListNode* n7 = (struct ListNode*)malloc(sizeof(struct ListNode));
n7->val = 6;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
n5->next = n6;
n6->next = n7;
n7->next = NULL;
removeElements1(n1, 6);
removeElements2(n1, 6);
return 0;
}
2.反转一个链表<难度系数⭐>
📝 题述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
💨 示例1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
💨 示例2:
输入:head = [1,2]
输出:[2,1]
💨 示例3:
输入:head = [ ]
输出:[ ]
🧷 平台:Visual studio 2017 && windows
🔑 核心思想:
思路1,调整节点指针的方向
思路2,头插
#include<stdio.h>
#include<stdlib.h>
typedef int SLTDataType;
struct ListNode
{
int val;
struct ListNode *next;
};
//思路1
struct ListNode* reverseList1(struct ListNode* head)
{
//空链表
if(head == NULL)
return head;
struct ListNode* n1, *n2, *n3;
n1 = NULL;
n2 = head;
n3 = head->next;
while(n2)
{
n2->next = n1;
n1 = n2;
n2 = n3;
//最后一次进来时,n3为空指针
if(n3)
n3 = n3->next;
}
return n1;
}
//思路2
struct ListNode* reverseList2(struct ListNode* head)
{
struct ListNode *newnode, *cur, *next;
newnode = NULL;
cur = head;
while(cur)
{
next = cur->next;
cur->next = newnode;
newnode = cur;
cur = next;
}
return newnode;
}
int main()
{
struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
n1->val = 1;
struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
n2->val = 2;
struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
n3->val = 3;
struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
n4->val = 4;
struct ListNode* n5 = (struct ListNode*)malloc(sizeof(struct ListNode));
n5->val = 5;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
n5->next = NULL;
reverseList1(n1);
reverseList2(n1);
return 0;
}
3.链表的中间结点<难度系数⭐>
📝 题述:给定一个头结点为 head 的非空单链表,返回链表的中间结点。 如果有两个中间结点,则返回第二个中间结点。
💨 示例1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。注意,我们返回了一个 ListNode 类型的对象 ans,这样:ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
💨 示例2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
🧷 平台:Visual studio 2017 && windows
🔑 核心思想:
快慢指针,快指针走2步,慢指针走1步,当快指针走完后,慢指针的当前位置就是中间节点
#include<stdio.h>
#include<stdlib.h>
typedef int SLTDataType;
struct ListNode
{
int val;
struct ListNode *next;
};
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode *slow, *fast;
slow = fast = head;
//必须同时满足奇偶的条件
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
int main()
{
struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
n1->val = 1;
struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
n2->val = 2;
struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
n3->val = 3;
struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
n4->val = 4;
struct ListNode* n5 = (struct ListNode*)malloc(sizeof(struct ListNode));
n5->val = 5;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
n5->next = NULL;
middleNode(n1);
return 0;
}
4.链表中倒数第k个节点<难度系数⭐>
📝 题述:输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
💨 示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5
🧷 平台:Visual studio 2017 && windows
🔑 核心思想:
快慢指针,快指针先走k步,此时它与慢指针相差的就是k步,然后快慢指针同时1步1步的走,直到快指针指向NULL,此时慢指针指向的节点就是目标节点
⚠ 这道题我在两个平台都做了下,按照常规的写法,发现它在leetcode能过的代码,在牛客网上却过不了
所以我们这里使用更严格的写法
#include<stdio.h>
#include<stdlib.h>
typedef int SLTDataType;
struct ListNode
{
int val;
struct ListNode *next;
};
struct ListNode* getKthFromEnd(struct ListNode* head, int k)
{
if(k == 0)
return NULL;
struct ListNode *slow, *fast;
slow = fast = head;
//fast先走k步
while(k--)
{
//如果k大于链表的长度
if(fast == NULL)
return NULL;
fast = fast->next;
}
//slow和fast同时走,直至fast指向空
while(fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
int main()
{
struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
n1->val = 1;
struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
n2->val = 2;
struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
n3->val = 3;
struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
n4->val = 4;
struct ListNode* n5 = (struct ListNode*)malloc(sizeof(struct ListNode));
n5->val = 5;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
n5->next = NULL;
getKthFromEnd(n1, 2);
return 0;
}
5.合并两个有序链表<难度系数⭐>
📝 题述:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
💨 示例1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
💨 示例2:
输入:l1 = [], l2 = []
输出:[ ]
💨 示例3:
输入:l1 = [ ], l2 = [0]
输出:[0]
🧷 平台:Visual studio 2017 && windows
🔑 核心思想:
思路1,见下图
思路2,带哨兵位的头节点
在之前的文章中没有了解过什么是哨兵位头节点,在这里就了解下:
1️⃣ 哨兵位头节点,这个节点不存储有效数据;而非哨兵位头节点存储有效数据
2️⃣ 非哨兵位头节点如果要修改头指针,需要使用二级指针操作;而哨兵位头节点,则不会修改头指针,因为它不存储有效数据
3️⃣ 这种哨兵位头节点在实际中很少出现,包括哈希桶、临接表做子结构都是不带头的。其次就是OJ中给的链表都是不带头的(从上面做的这些题就可以看出),所以我们在以后编码过程中,应该尽量使用不带头的
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef int SLTDataType;
struct ListNode
{
int val;
struct ListNode *next;
};
//思路1
struct ListNode* mergeTwoLists1(struct ListNode* l1, struct ListNode* l2)
{
struct ListNode *n1 = l1, *m1 = l2;
struct ListNode *newhead = NULL, *tail = NULL;
//判断空链表
if(l1 == NULL)
return l2;
if(l2 == NULL)
return l1;
while(n1 && m1)
{
if(n1->val < m1->val)
{
if(newhead == NULL)
{
newhead = tail = n1;
}
else
{
tail->next = n1;
tail = n1;
}
n1 = n1->next;
}
else
{
if(newhead == NULL)
{
newhead = tail = m1;
}
else
{
tail->next = m1;
tail = m1;
}
m1 = m1->next;
}
}
//链接上剩余的数据
if(n1)
tail->next = n1;
if(m1)
tail->next = m1;
return newhead;
}
//思路2
struct ListNode* mergeTwoLists2(struct ListNode* l1, struct ListNode* l2)
{
struct ListNode *n1 = l1, *m1 = l2;
struct ListNode *newhead = NULL, *tail = NULL;
//判断空链表
if(l1 == NULL)
return l2;
if(l2 == NULL)
return l1;
//申请空间
newhead = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
while(n1 && m1)
{
if(n1->val < m1->val)
{
tail->next = n1;
tail = n1;
n1 = n1->next;
}
else
{
tail以上是关于链表学会了没,会做这些题就足够了,思路全在动图里了,不懂都难《上篇》的主要内容,如果未能解决你的问题,请参考以下文章
链表学会了没,会做这些题就足够了,思路全在动图里了,不懂直接剁手《下篇》