“我不唱声嘶力竭的情歌,不表示没有心碎的时刻”——《孤独患者》
经过上期链表OJ(上)
相信大家也都摩拳擦掌准备迎接这期难度稍高的链表OJ下啦,冲冲冲!
题目链接 :CM11 链表分割
题目描述:
现有一链表的头指针1
ListNode* pHead
,给一定值x
,编写一段代码将所有小于x
的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
思路:类似于上期合并两个有序链表的思路,这次我们要用两个链表,为了简化我们的逻辑,使用带哨兵的链表。
1. 遍历原链表,一个存放值小于x
的节点,另一个存值大于等于x
的节点。
2. 将两个链表连接起来
3. 记录要返回的头,释放哨兵
图示:
代码实现:
class Partition {
public:
ListNode* partition(ListNode* pHead, int x)
{
ListNode* LessHead=new ListNode(0);
ListNode* GreaterHead=new ListNode(0);
ListNode *LessTail=LessHead, *GreaterTail=GreaterHead;
for(ListNode* cur=pHead;cur;cur=cur->next)
{
if(cur->val<x)
{
LessTail->next=cur;
LessTail=cur;
}
else
{
GreaterTail->next=cur;
GreaterTail=cur;
}
}
LessTail->next=GreaterHead->next;
GreaterTail->next=NULL;
ListNode* head=LessHead->next;
delete LessHead;
delete GreaterHead;
return head;
}
};
题目链接:链表的回文结构
题目描述:
对于一个链表,请设计一个时间复杂度为
O(n)
,额外空间复杂度为O(1)
的算法,判断其是否为回文结构。给定一个链表的头指针
A
,请返回一个bool
值,代表其是否为回文结构。保证链表长度小于等于900
。
我们遇到的困难主要是这是个单向的链表。没法向前访问节点,需要能“逆序”
- 找中间节点(快慢指针,详见OJ(上))
- 逆序中间节点之后的链表
- 判断前后是否一样
图示:
代码实现:
//返回一个链表的中间节点
//快慢节点
ListNode* middleNode(ListNode *head)
{
ListNode *slow,*fast;
fast=slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
//逆置链表,返回新的头节点
// 1.三个指针翻转链表
ListNode* reverseList1(ListNode* head)
{
ListNode *prev=NULL,*cur=head;
while(cur)
{
ListNode* next=cur->next;
cur->next=prev;
prev=cur;
cur=next;
}
return prev;
}
//2.头插法翻转链表
ListNode* reverseList2(ListNode* head)
{
ListNode* newHead=NULL;
ListNode* cur=head;
while(cur)
{
ListNode *next=cur->next;
cur->next=newHead;
newHead=cur;
cur=next;
}
return newHead;
}
class PalindromeList {
public:
bool chkPalindrome(ListNode* A)
{
//获得中间节点
ListNode* middle=middleNode(A);
//将中间节点开始往后的链表逆置
//ListNode* rHead=reverseList1(middle);
ListNode* rHead=reverseList2(middle);
while(A&&rHead)
{
if(A->val==rHead->val)
{
A=A->next;
rHead=rHead->next;
}
else
return false;
}
return true;
}
};
!!!不推荐这种写法!!!
因为这种写法改变了传入链表的结构,而我们这边的需求只是判断一个链表是否具有回文结构。
1. 找中间节点
2. 把头至中间节点(不包括)入栈
3. 从中间节点开始,栈顶元素->val和cur->val相等就出栈
4. 最后判断栈是否为空
代码实现:
class PalindromeList {
public:
bool chkPalindrome(ListNode* A)
{
ListNode *fast,*slow,*middle,*cur;
fast=slow=A;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
middle=slow;
stack<ListNode*> st;
for(cur=A;cur!=middle;cur=cur->next)
st.push(cur);
for(cur=middle;cur;cur=cur->next)
{
if(st.top()->val==cur->val)
st.pop();
}
if(st.empty())
return true;
return false;
}
};
题目链接:160. 相交链表
题目描述:
给你两个单链表的头节点
headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null
。图示两个链表在节点
c1
开始相交:来源:力扣(LeetCode)
思路:
要判断链表是否相交比较简单,两个链表都走到尾,看最后一个节点是否相同即可(因为链表相交一定是这种倒着的Y型,而不可能是X型)
我们还需要返回第一个相交节点,如果两个链表长度相同,一起遍历,如果节点相同直接返回即可。
在长度不同的情况下,长的先走gap
步,再一起走即可
代码实现:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *pa=headA,*pb=headB;
int lenA=1,lenB=1;
while(pa->next)
{
lenA++;
pa=pa->next;
}
while(pb->next)
{
lenB++;
pb=pb->next;
}
if(pa!=pb)
return NULL;
ListNode *shortList=headA,*longList=headB;
if(lenA>lenB)
{
shortList=headB;
longList=headA;
}
int gap=abs(lenA-lenB);
while(gap--)
longList=longList->next;
while(shortList&&longList)
{
if(shortList==longList)
return longList;
longList=longList->next;
shortList=shortList->next;
}
return NULL;
}
};
题目链接:138. 复制带随机指针的链表
题目描述:
给你一个长度为
n
的链表,每个节点包含一个额外增加的随机指针random
,该指针可以指向链表中的任何节点或空节点。构造这个链表的 深拷贝。 深拷贝应该正好由
n
个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的next
指针和random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。例如,如果原链表中有
X
和Y
两个节点,其中X.random --> Y
。那么在复制链表中对应的两个节点x
和y
,同样有x.random --> y
。返回复制链表的头节点。
来源:力扣(LeetCode)
示意图(源自力扣):
这题是本blog中难度最高的一题,无论是从思路上还是代码的控制上都有一定的挑战性
首先我们要理解深拷贝
这是C++中和浅拷贝相对的一个概念。这边我们不过多深入的讨论(后续我会在C++专题中深入讨论)
此处可以理解为,我们再内存中自己申请节点,再按照这个链表连接的样子,来把这些节点连接起来
“如果原链表中有 X
和Y
两个节点,其中 X.random --> Y
。那么在复制链表中对应的两个节点 x
和 y
,同样有x.random --> y
”
是一个**“照猫画虎“**的过程。
分析:
如果不含random指针,这个过程是很简单的。遍历原链表,复制一个当前节点,按顺序建立一个新表即可。
但有了random指针,问题就复杂起来了。
主要在于我们在原表中,通过random指针可以知道某个节点的random指针指向哪个节点,但是在建立新表的过程中,我们并找不到新表的random指针该指向哪个新表中的节点(即新表和原表节点之间并无联系)
这个时候我们要解决这个问题,必须在原表与新表之间建立一种联系
我们采取这样一种做法:
把自己申请的拷贝节点连接在原节点的后面。
这样通过random指针找到的原节点的后一个就是新表中对应拷贝节点的random指针指向的拷贝节点。
图像解析:
解法:
1. 将拷贝节点连接到原节点后面
2. 处理拷贝节点的random指针
3. 将拷贝节点在原链表上”剪“下来并连接形成新表
代码实现:
typedef struct Node Node;
struct Node* copyRandomList(struct Node* head)
{
//1.将拷贝节点连接到原节点后面
Node* cur=head;
while(cur)
{
Node* next=cur->next;
Node* copy=(Node*)malloc(sizeof(Node));
copy->val=cur->val;
copy->next=next;
cur->next=copy;
cur=next;
}
//2.处理拷贝节点的random指针
cur=head;
while(cur)
{
Node* copy=cur->next;
if(!(cur->random))
copy->random=NULL;
else
copy->random=cur->random->next;
cur=copy->next;
}
//3.将拷贝节点剪下来并连接
cur=head;
Node *copyhead,*copytail;
copyhead=copytail=NULL;
while(cur)
{
Node* copy=cur->next;
if(copyhead==NULL)
copyhead=copy;
else
copytail->next=copy;
copytail=copy;
cur=cur->next=copy->next;
}
return copyhead;
}
到此我们这博客也接近尾声啦。
希望大家能在阅读完后有所收获!!!这将是对我最大的激励!
敲代码,码字,作图不易,期待一个小小的点赞❤❤
如果你对博客内容有什么疑惑,或者对思考题有什么不解,很欢迎大家来和我交流讨论哦✔
本期所有的代码我将传到我的gitee仓库中,如有需要可自行下载
仓库传送门:数据结构
我们下篇博客见!