序号也接着上篇了,贴个链接:
数据结构与算法⑤(第二章OJ题,上)前五道链表面试题_GR C的博客-CSDN博客
6. 分割链表成两部分
面试题 02.04. 分割链表
难度中等
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,
使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你不需要 保留 每个分区中各节点的初始相对位置。
示例 1:
输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
示例 2:
输入:head = [2,1], x = 2
输出:[1,2]
提示:
链表中节点的数目在范围 [0, 200] 内
-100 <= Node.val <= 100
-200 <= x <= 200
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* partition(struct ListNode* head, int x){
}
这题在牛客上标注较难,多了一个限制条件,且没有C语言提交的方式,但C++兼容C,用C++交就行
CM11 链表分割 链接:链表分割_牛客题霸_牛客网 (nowcoder.com)
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
哨兵位头结点+尾插代码:
struct ListNode* partition(struct ListNode* head, int x) {
//开哨兵位头节点 方便尾插
struct ListNode* small_head = (struct ListNode*)malloc(sizeof(struct ListNode));
small_head->next = NULL;
struct ListNode* large_head = (struct ListNode*)malloc(sizeof(struct ListNode));
large_head->next = NULL;
struct ListNode* small_tail = small_head;
struct ListNode* large_tail = large_head;
while (head)
{
if (head->val < x)
{
small_tail->next = head;
small_tail = small_tail->next;
}
else
{
large_tail->next = head;
large_tail = large_tail->next;
}
head = head->next;
}
large_tail->next = NULL;//易忘记 可能成环
small_tail->next = large_head->next;
struct ListNode* new_head = small_head->next;
free(small_head);//临时指针可以不置空
free(large_head);
return new_head;
}
7. 链表的回文结构
234. 回文链表
(这两题是一模一样的)
难度简单
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;
否则,返回 false 。
示例 1:
输入:head = [1,2,2,1]
输出:true
示例 2:
输入:head = [1,2]
输出:false
提示:
链表中节点数目在范围[1, 10^5] 内
0 <= Node.val <= 9
进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool isPalindrome(struct ListNode* head){
}
这题也在牛客上标注较难
OR36 链表的回文结构 链接:链表的回文结构_牛客题霸_牛客网 (nowcoder.com)
描述
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
测试样例:
1->2->2->1
返回:true
快慢指针+反转代码:
这题可以根据给的范围开出最大范围的数组,空间也是O(1)然后用数组判断回文,但不建议这么做
可以直接用前五道写过的找中间节点和反转函数的函数,但不熟还是要多写
画图发现如果不解链表的话也能找到终止条件
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* newHead = NULL, * cur = head;
while (cur)
{
struct ListNode* curNext = cur->next;
cur->next = newHead;//头插
newHead = cur;//迭代往后走
cur = curNext;
}
return newHead;
}
struct ListNode* middleNode(struct ListNode* head) {
struct ListNode* fast = head, * slow = head;
while (fast != NULL && fast->next != NULL)
{
fast = fast->next->next;//快指针一次走两步
slow = slow->next;//慢指针一次走一步
}
return slow;
}
bool isPalindrome(struct ListNode* head) {
struct ListNode* mid = middleNode(head);
struct ListNode* reverse_mid = reverseList(mid);
while (head && reverse_mid)
{
if (head->val != reverse_mid->val)
{
return false;
}
else
{
head = head->next;
reverse_mid = reverse_mid->next;
}
}
return true;
}
8. 找两个链表的第一个公共结点
160. 相交链表
(链接题目都是一样的,给这么多链接只是为了知道这题在很多地方出现)
难度简单
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA - 第一个链表
listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
示例 2:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
提示:
listA 中节点数目为 m
listB 中节点数目为 n
1 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB]
进阶:你能否设计一个时间复杂度 O(m + n) 、仅用 O(1) 内存的解决方案?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
}
思路1 :暴力求解-穷举
依次取A链表中的每个节点跟B链表中的所有节点比较
如果有地址相同的节点,就是相交,返回第一个相同的交点
时间:O(N^2) 不符合题意
思路2: 时间要求优化到O(N)
1、尾节点相同就是相交,否则就不相交
2、求交点:长的链表先走(长度差)步,再同时走,返回第一个相同的交点
代码:
struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB) {
struct ListNode* tailA = headA;
struct ListNode* tailB = headB;
int lenA = 1, lenB = 1;
while (tailA->next)
{
tailA = tailA->next;
lenA++;
}
while (tailB->next)
{
tailB = tailB->next;
lenB++;
}
if (tailA != tailB)
{
return NULL;
}
int gap = abs(lenA - lenB);
//gap 差值 abs求整数绝对值函数 头文件:stdlib.h
while (gap--)
{
if (lenA > lenB)
{
headA = headA->next;
}
else if (lenB > lenA)
{
headB = headB->next;
}
}
while (headA != headB)//注意不能比较next又返回next(可能为空)血的教训
{
headA = headA->next;
headB = headB->next;
}
return headA;
}
评论区大诗人的代码:
(程序猿的浪漫)笑
//朋友们,请一定要珍惜身边的那个 ta 啊!你们之所以相遇,正是因为你走了 ta 走过的路,
//而 ta 也刚好走了你走过的路。这是何等的缘分!
//而当你们携手继续走下去时,你会慢慢变成 ta 的样子,ta 也会慢慢变成你的样子。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (!headA || !headB) {
return NULL;
}
ListNode *you = headA, *she = headB;
while (you != she) { // 若是有缘,你们早晚会相遇
you = you ? you->next : headB; // 当你走到终点时,开始走她走过的路
she = she ? she->next : headA; // 当她走到终点时,开始走你走过的路
}
// 如果你们喜欢彼此,请携手一起走完剩下的旅程(将下面这个 while 块取消注释)。
// 一路上,时而你踩着她的影子,时而她踩着你的影子。渐渐地,你变成了她,她也变
//成了你。
/* while (she) {
you = she->next;
she = you->next;
} */
return you;
}
};
9. 判断链表中是否有环
141. 环形链表
难度简单
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置
(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 10^4]
-10^5 <= Node.val <= 10^5
pos 为 -1 或者链表中的一个 有效索引 。
进阶:你能用 O(1)(即,常量)内存解决此问题吗?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
}
暴力求解代码:
自己按照给的范围取巧写的代码:
看完别人的快慢指针和证明又是震惊的一天
bool hasCycle(struct ListNode* head) {
struct ListNode* head1 = head, * head2 = head;
int x = 10000, y = 10000;
while (x-- && head1 && head2)
{
while (y-- && head2)
{
if (head2->next == head1)
{
return true;
}
head2 = head2->next;
}
head1 = head1->next;
if (head2)
{
head2 = head;
}
}
return false;
}
快慢指针代码:
就是在环里,下面走两步的快指针一定会追上慢指针
bool hasCycle(struct ListNode* head) {
struct ListNode* fast = head, * slow = head;
while (fast && fast->next)
{
if (fast->next == slow)
{
return true;
}
slow = slow->next;
fast = fast->next->next;
}
return false;
}
为什么fast一定会和slow相遇呢?
为什么fast要走两步,slow走一步呢?
面试的时候不会让写代码,但会问下面的两个延伸问题
(图片拼起来的,看不清楚的话点开放大看吧,很重要)
10. 返回链表入环的第一个节点
142. 环形链表 II
难度中等
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 10^4] 内
-10^5 <= Node.val <= 10^5
pos 的值为 -1 或者链表中的一个有效索引
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
}
公式+快慢指针代码:
首先要知道,根据上面的距离的递减,fast在slow进环后最多走两圈,slow最多走一圈。
fast就会追上slow。
以下是公式推导证明
所以根据公式L = (N - 1) * C + C * X
得出结论:一个指针从相遇点开始走,一个指针走表头开始走,他们会在环的入口点相遇。
(L是一个指针从表头开始走到入口点的距离,C * X 是相遇点往后走到表头的距离,相等,就相遇。)
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode* fast = head, * slow = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (fast == slow)
{
struct ListNode* meet = slow;
while (meet != head)
{
meet = meet->next;//一个指针从相遇点走
head = head->next;//一个指针从头走
}
return meet;//会在入环的第一个节点相遇
}
}
return NULL;
}
另一种方法:转化成链表相交
如果要求不破坏链表长度,可以让head当尾,meet当头
11. 返回链表的深度拷贝
138. 复制带随机指针的链表
难度中等
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,
该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。
那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
提示:
0 <= n <= 1000
-10^4 <= Node.val <= 10^4
Node.random 为 null 或指向链表中的节点。
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
struct Node* copyRandomList(struct Node* head) {
}
神奇代码:
很复杂的一题,要对链表增删查改很熟悉
可以用时间O(N^2)暴力法通过,会发现random非常的难处理
(因为原链表random的指向的节点在复制链表的对应的要指向的节点找不到了)
O(N^2)暴力法就是比较值是否相等,但是值有两个呢,这样又要麻烦了
以下是时间O(N)的很巧妙的方法:
struct Node* copyRandomList(struct Node* head) {
//1.插入复制节点到原节点的后面
struct Node* cur = head;
while (cur)
{
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
copy->next = cur->next;//插入copy节点
cur->next = copy;
cur = copy->next;
}
//2.根据原节点处理复制节点的random
cur = head;
while (cur)
{
struct Node* copy = cur->next;
if (cur->random == NULL)
{
copy->random = NULL;
}
else
{
copy->random = cur->random->next;
}
cur = copy->next;
}
//3.复制节点解下来链接成一个新链表,恢复原链表链接关系
struct Node* copy_head = NULL, * copy_tail = NULL;
cur = head;
while (cur)
{
struct Node* copy = cur->next;
struct Node* next = copy->next;
if (copy_head == NULL)//第一次进来
{
copy_head = copy_tail = copy;
}
else
{
copy_tail->next = copy;
copy_tail = copy;
}
cur->next = next;
cur = next;
}
return copy_head;
}
下面是后补的两道:
12. 对链表进行插入排序
(因为是穿越回来补的两道,所以对插入排序不懂的可以看看下面的链接:)
数据结构与算法⑰(第五章_八大排序)(完整代码+动图+详解+对比)_GR C的博客-CSDN博客
147. 对链表进行插入排序
难度中等
给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。
示例 1:
输入: head = [4,2,1,3]
输出: [1,2,3,4]
示例 2:
输入: head = [-1,5,3,4,0]
输出: [-1,0,3,4,5]
提示:
列表中的节点数在 [1, 5000]范围内
-5000 <= Node.val <= 5000
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* insertionSortList(struct ListNode* head){
}
代码:
struct ListNode* insertionSortList(struct ListNode* head) {
struct ListNode* new_head = malloc(sizeof(struct ListNode));
new_head->val = 0;
new_head->next = head;//新开一个节点指向头,最后返回这个节点的next
struct ListNode* end = head;//设置end是已经插入好的链表的最后一个
struct ListNode* cur = head->next;
while (cur)
{
if (end->val <= cur->val) //找比 已经插入好的最后一个 小的
{
end = end->next;
}
else
{
struct ListNode* prev = new_head;
while (prev->next->val <= cur->val) //找到了就再找cur应该插入的位置
{
prev = prev->next;
}
//插入
end->next = cur->next;
cur->next = prev->next;
prev->next = cur;
}
cur = end->next;//cur=已经插入好的链表的最后一个的下一个,重复循环
}
struct ListNode* ret = new_head->next;
free(new_head);
return ret;
}
13. 删除链表中重复的结点
删除链表中重复的结点_牛客题霸_牛客网 (nowcoder.com)
JZ76 删除链表中重复的结点
中等 通过率:22.17% 时间限制:1秒 空间限制:64M
描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5
数据范围:链表长度满足 0≤n≤1000 ,链表中的值满足 1≤val≤1000
进阶:空间复杂度 O(n) ,时间复杂度 O(n)
例如输入{1,2,3,3,4,4,5}时,对应的输出为{1,2,5},对应的输入输出链表如下图所示:
示例1
输入:
{1,2,3,3,4,4,5}
复制
返回值:
{1,2,5}
复制
示例2
输入:
{1,1,1,8}
复制
返回值:
{8}
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @return ListNode类
*/
struct ListNode* deleteDuplication(struct ListNode* pHead ) {
}
代码:
struct ListNode* deleteDuplication(struct ListNode* pHead ) {
if(pHead == NULL)
{
return NULL;
}
struct ListNode* new_head = malloc(sizeof(struct ListNode));
new_head->val = 0;
new_head->next = pHead;//新开一个节点指向头,最后返回这个节点的next
struct ListNode* cur = new_head;
while(cur->next && cur->next->next)
{
if(cur->next->val == cur->next->next->val)
{
int tmp = cur->next->val;//遇到两个节点相同的,记录下来并跳过
while(cur->next && tmp == cur->next->val)
{
cur->next = cur->next->next;
}
}
else
{
cur = cur->next;
}
}
return new_head->next;
}
严谨代码:
ListNode* deleteDuplication(ListNode* pHead)
{
if (pHead == NULL || pHead->next == NULL)
return pHead;
struct ListNode* n0 = NULL;
struct ListNode* n1 = pHead;
struct ListNode* n2 = n1->next;
while (n2 != NULL)
{
//如果相邻节点不相同,则不需要删除,更新节点,继续向后遍历
if (n1->val != n2->val)
{
n0 = n1;
n1 = n2;
n2 = n2->next;
}
else
{
//如果相邻节点相同
//则n2去找第一个不相同的节点
while (n2 && n2->val == n1->val)
{
n2 = n2->next;
}
//重新链接,如果要删除的包括头节点,则更新头节点
if (n0)
n0->next = n2;
else
pHead = n2;
// 删除掉重复的节点
while (n1 != n2)
{
struct ListNode* next = n1->next;
free(n1);
n1 = next;
}
//更新节点
n1 = n2;
if (n2)
n2 = n2->next;
}
}
return pHead;
}
链表其它题链接:
力扣:https://leetcode.cn/tag/linked-list/problemset/
牛客https://www.nowcoder.com/exam/oj
本篇完。
(下篇实现带头双向循环链表,穿越回来复习顺便贴个链接:
数据结构与算法⑦(第二章收尾)带头双向循环链表的实现_GR C的博客-CSDN博客)
有时间后面在回来看吧