链表OJ
目录
试题1:返回倒数第k个节点
题目内容:
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值
示例:
输入:1->2->3->4->5 和 k = 2
输出:4
思路1:快慢指针法
思路解析:
创建一个慢指针slow,快指针fast
快指针先走k步,然后快慢指针同时走
快指针指向空后结束,此时slow指向倒数第k个节点
代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
int kthToLast(struct ListNode* head, int k)
{
struct ListNode* fast = head,*slow = head;
/*快指针先走k步*/
while(k--)
{
fast = fast->next;
}
/*快慢指针同时走*/
while(fast)
{
slow = slow->next;
fast = fast->next;
}
return slow->val;
}
试题2:链表的回文结构
题目内容:
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构,给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构,保证链表长度小于等于900。
示例:
输入:1->2->2->1
输出:true
思路1:逆置法
思路解析:
查找中间节点,逆置后半段节点,将头节点与中间节点往后的数据依次进行比较
相同继续往后走,不同返回false,直到有一个指向空后结束循环,返回true
代码部分:
cpp
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList
{
public:
struct ListNode* reverseList(struct ListNode* head)
{
if(head == NULL)
{
return head;
}
ListNode* n1,*n2,*n3;
n1 = NULL;n2 = head;n3 = n2->next;
while(n2)
{
n2->next = n1;
n1 = n2;
n2 = n3;
if(n3)
{
n3 = n3->next;
}
}
return n1;
}
struct ListNode* middleNode(struct ListNode* head)
{
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
bool chkPalindrome(ListNode* A)
{
/*查找中间节点*/
struct ListNode* mid = middleNode(A);
/*逆置后半段节点*/
struct ListNode* rmid = reverseList(mid);
while(rmid && A)
{
if(rmid->val != A->val)
{
return false;
}
rmid = rmid->next;
A = A->next;
}
return true;
}
};
试题3:相交链表
题目内容:
给你两个单链表的头节点headA和headB,请你找出并返回两个单链表相交的起始节点,如果两个链表不存在相交节点,返回NULL,图示两个链表在节点c1开始相交**:**
示例:
输入:intersectVal = 8,listA = [4,1,8,4,5],listB = [5,6,1,8,4,5],skipA = 2,skipB = 3
输出:Intersected at '8'
思路解析:
判断相交:如果A链表和B链表的尾节点地址相同则相交
寻找交点:
- 法1:A链表的每个节点依次跟B链表的所有节点比较,当A链表某个节点地址和B链表某个节点地址相等,这个节点就是交点,这种方法的时间复杂度为O(N^2)
- 法2:先算出两个链表节点个数差,让长链表先走差距步,然后两个链表同时走,第一个相等的就是节点,这种方法的时间复杂度为O(N),相比法1效率更高
代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode {
int val;
struct ListNode *next;
};
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode* curA = headA,*curB = headB;
int lenA = 1,lenB = 1;
/*找两个链表的尾节点*/
while(curA->next)
{
curA = curA->next;
++lenA;
}
while(curB->next)
{
curB = curB->next;
++lenB;
}
/*尾节点地址不同就不相交*/
if(curA != curB)
{
return NULL;
}
/*计算两个链表节点个数差*/
int gap = abs(lenA - lenB);
/*假设法,先假设A长,B短*/
struct ListNode* longList = headA,*shortList = headB;
/*如果假设错误,则A短,B长*/
if(lenB > lenA)
{
longList = headB;
shortList = headA;
}
/*长的先走差距步*/
while(gap--)
{
longList = longList->next;
}
/*同时往后走*/
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return shortList;
}
试题4:环形链表
题目内容:
给你一个链表的头节点head,判断链表中是否有环
如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环
为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)注意:pos不作为参数进行传递 ,仅仅是为了标识链表的实际情况
如果链表中存在环 ,则返回true,否则返回false
示例:
输入:head = [3,2,0,-4],pos = 1
输出:true
思路1:快慢指针法
思路解析:
创建一个快指针fast,慢指针slow
慢指针走一步,快指针走两步
fast先进入环形链表,slow后进入环形链表
若快指针与慢指针相遇,则说明链表成环
代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
bool hasCycle(struct ListNode *head)
{
struct ListNode* slow = head,*fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(fast == slow)
{
return true;
}
}
return false;
}
**问题1:**证明当慢指针走一步,快指针走两步时两个指针一定相遇?
假设慢指针进环时,快慢指针距离为n,每追击一次,距离缩小1,当距离为0时相遇
**问题2:**证明当慢指针走一步,快指针走三步时是否会相遇?
假设进环前长度为L,环长为C,慢指针进环时,距离差为N
快指针走的距离:L + x*C + C - N
慢指针走的距离:L
由于:3 * slow = fast
所以:3L = L + x*C + C - N
2L = ( x + 1 ) * C - N
左边的式子一定为偶数
当C为偶数,N为奇数时:
偶数 * 任何数 == 偶数:(x+1)*c为偶数
偶数 - 奇数 == 奇数:右边结果为奇数
所以左边为偶数,右边是奇数,一定追不上
当C为奇数,N为偶数时
若x+1为偶数 偶数 * 奇数 == 偶数 偶数 - 偶数 == 偶数
若x+1为奇数 奇数 * 奇数 == 奇数 奇数 - 偶数 == 奇数
所以当x+1为偶数时,左右为偶数,可能追上
当C为偶数,N为偶数时
(x+1)*c为偶数 偶数 - 偶数 == 偶数
所以左边和右边都为偶数,一定能追得上
当C为奇数,N为奇数时
若x+1为奇数 奇数 * 奇数 == 奇数 奇数 - 奇数 = 偶数
若x+1为偶数 偶数 * 奇数 == 偶数 偶数 - 奇数 = 奇数
所以当x+1为奇数时,左右为偶数,可能追上
拓展:
若速度差与环长互质,就一定能够相遇
若不互质,只有当初始距离为它们最大公约数的倍数时,才能相遇
试题5:环形链表II
题目内容:
给定一个链表的头节点head,返回链表开始入环的第一个节点,如果链表无环,则返回NULL,如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环,为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)如果pos是-1,则在该链表中没有环
示例:
输入:head = [3,2,0,-4],pos = 1
输出:返回索引为1的链表节点
注意:
pos不作为参数进行传递,仅仅是为了标识链表的实际情况,不允许修改链表
思路1:快慢指针法
思路解析:
创建一个快指针fast,慢指针slow
慢指针走一步,快指针走两步
当快慢指针相遇时,创建meet指针指向相遇的位置
meet与head同时走,每次走一步
当meet与head相遇时就是入环节点
代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* slow = head,*fast = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(slow == fast)
{
struct ListNode* meet = slow;
while(meet != head)
{
meet = meet->next;
head = head->next;
}
return meet;
}
}
return NULL;
}
**问题1:**为什么meet与head相遇位置就是入环节点位置?
假设进环前长度为L,环长为C,慢指针进环时,距离差为N
快指针每次走两步,慢指针每次走一步
相遇时,slow走的路程为L+N,fast走的路程为L + x*C + N
由于:2 * slow = fast
所以:2*(L + N) = L + x*C + N
L = x*C - N等价于L = C - N
即从链表头走到环入口的距离大于从相遇点走到环入口的距离
思路2:断环法
思路解析:
创建临时变量newhead存放相遇节点meet的下一个节点
将相遇节点meet的指针域置为NULL,断开环形链表
从初始节点与下部分断环处节点开始
寻找进环链表,与下断环链表的交点
代码部分:
cpp
/*
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode* curA = headA,*curB = headB;
int lenA = 1,lenB = 1;
while(curA->next)
{
curA = curA->next;
++lenA;
}
while(curB->next)
{
curB = curB->next;
++lenB;
}
if(curA != curB)
{
return NULL;
}
int gap = abs(lenA - lenB);
struct ListNode* longList = headA,*shortList = headB;
if(lenB > lenA)
{
longList = headB;
shortList = headA;
}
while(gap--)
{
longList = longList->next;
}
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return shortList;
}
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* slow = head,*fast = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(slow == fast)
{
struct ListNode* meet = slow;
struct ListNode* newhead = meet->next;
meet->next = NULL;
return getIntersectionNode(head,newhead);
}
}
return NULL;
}
试题6:随机链表的复制
题目内容:
给你一个长度为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作为传入参数
示例:
输入:head = [[7,NULL],[13,0],[11,4],[10,2],[1,0]]
输出: [[7,NULL],[13,0],[11,4],[10,2],[1,0]]
思路1:拷贝法
思路解析:
把拷贝节点插入在原节点的后面
拷贝节点的数值为当前节点的数值
拷贝节点的next指针变量存放当前节点下一个节点的地址
拷贝节点的random指针变量存放当前节点random指针指向节点的地址

把拷贝节点取下来尾插成为新链表,恢复原链表

代码部分:
cpp
/*
Definition for a Node.
struct Node
{
int val;
struct Node *next;
struct Node *random;
};
*/
struct Node* copyRandomList(struct Node* head)
{
struct Node* cur = head;
//拷贝节点插入原节点后面
while(cur)
{
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
copy->next = cur->next;
cur->next = copy;
cur = copy->next;
}
//控制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;
}
//把拷贝节点取下来尾插成为新链表,然后恢复原链表
struct Node* copyhead = NULL,*copytail = NULL;
cur = head;
while(cur)
{
struct Node* copy = cur->next;
struct Node* next = copy->next;
if(copytail == NULL)
{
copyhead = copytail = copy;
}
else
{
copytail->next = copy;
copytail = copytail->next;
}
cur->next = next;
cur = next;
}
return copyhead;
}
七、总结
本篇博客是对于数据结构中链表OJ的整理归纳,后续还会更新栈和队列等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~




