今天鼠鼠带你玩转链表~
你间歇性的努力和蒙混过日子,都是对之前努力的清零!
目录
1.2 138. 复制带随机指针的链表 - 力扣(LeetCode)
1.3 203. 移除链表元素 - 力扣(LeetCode)
2.1 876. 链表的中间结点 - 力扣(LeetCode)
2.2 链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)
3.1 链表的回文结构_牛客题霸_牛客网 (nowcoder.com)
3.4 142. 环形链表 II - 力扣(LeetCode)
前言
如果你不熟悉链表概念和链表基本的代码,我写了另一篇博客:顺序表和链表 让你了解
一、操作链表型OJ
1.1 206. 反转链表 - 力扣(LeetCode)
思路一:遍历链表头插至一个新链表
struct ListNode* reverseList(struct ListNode* head) {
//创建哨兵节点,其指向新链表头节点
struct ListNode*guard=(struct ListNode*)malloc(sizeof(struct ListNode));
guard->next=NULL;
//创建cur遍历原链表
struct ListNode*cur=head;
while(cur)
{
//先保存原链表下一个节点
struct ListNode*next=cur->next;
//头插至新链表
cur->next=guard->next;
guard->next=cur;
//迭代
cur=next;
}
//返回新链表头节点,释放哨兵节点
head=guard->next;
free(guard);
return head;
}
思路二:双指针遍历链表(后项变前项)
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* prev = NULL;//前项
struct ListNode* curr = head;//<-中间->
while (curr) {
struct ListNode* next = curr->next;//保存后项
curr->next = prev;//反转
prev = curr;//中间变前项
curr = next;//后项变中间
}
return prev;
}
1.2 138. 复制带随机指针的链表 - 力扣(LeetCode)
思路:本题要复制复杂链表,将这些结点复制链接起来不难。难点在于如何找到新创链表结点的random,有人会说遍历一遍,找到val相同的就是random指向的结点,这个观点是不对的!因为你不知道是否不止一个结点的val是这个值! 怎么办捏?最好的办法就是在原链表的每个结点的后面链接一个相同val的结点,通过原链表random结点,找到我们要找的random!然后再取出新链表,重新链接原链表
struct Node* copyRandomList(struct Node* head) {
struct Node*cur=head;
struct Node*copy=NULL;
struct Node*next=NULL;
while(cur)
{
//复制链接
next=cur->next;
struct Node*copy=(struct Node*)malloc(sizeof(struct Node));
copy->val=cur->val;
cur->next=copy;
copy->next=next;
//迭代
cur=next;
}
//更新copy->random
cur=head;
while(cur)
{
copy=cur->next;
if(cur->random==NULL)
{
copy->random=NULL;
}
else{
copy->random=cur->random->next;
}
cur=copy->next;
}
//解下copy链表,恢复原链表
cur=head;
struct Node*newhead=NULL;
struct Node*tail=NULL;
while(cur)
{
copy=cur->next;
next=copy->next;
if(newhead==NULL)
{
newhead=tail=copy;
}
else{
tail->next=copy;
tail=tail->next;
}
//恢复原链表
cur->next=next;
//迭代
cur=next;
}
return newhead;
}
1.3 203. 移除链表元素 - 力扣(LeetCode)
思路一:创建新链表尾插非val节点
struct ListNode* removeElements(struct ListNode* head, int val){
//创建哨兵节点,哨兵指向新链表头节点
struct ListNode*guard=(struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode*tail=guard;
guard->next=head;
//遍历原链表
while(tail&&tail->next)
{
//相等,说明此时的next节点不是我们要找的,更新自己的next
if(tail->next->val==val)
{
tail->next=tail->next->next;
}
//不同则迭代
else{
tail=tail->next;
}
}
//返回头节点,销毁guard
head=guard->next;
free(guard);
return head;
}
思路二:双指针遍历链表,一个指针寻找非val,一个指针保存上一个非val负责串连
struct ListNode* removeElements(struct ListNode* head, int val){
//寻找非val结点
while(head)
{
if(head->val==val){
head=head->next;
}
else{
break;
}
}
//判定是否为空链表+判断是否链表值全是Val
if(head==NULL)
{
return NULL;
}
struct ListNode* p1=head;//用于串联新链表
struct ListNode* p2=head->next;//用于寻找非Val结点
while(p2)
{
if(p2->val!=val)//判定是否为Val结点
{
//串联
p1->next=p2;
p1=p1->next;
//继续寻找
p2=p2->next;
}
else{
//继续寻找
p2=p2->next;
}
}
p1->next=NULL;//完成串联
return head;
}
1.4 方法总结
对于操作链表大致分为两种方法,一种是遍历,一种是递归,本篇文章主要围绕遍历来写。遍历其中也可以分为两种主要方法,一种是创建新链表(带哨兵节点),一种是双指针遍历串连,其中带哨兵节点更为简单方便,我主推这种方式解决问题。
二、查找链表指定位置节点
2.1 876. 链表的中间结点 - 力扣(LeetCode)
struct ListNode* middleNode(struct ListNode* head){
struct ListNode*slow=head,*fast=head;
while(fast&&fast->next)
{
slow=slow->next;//慢指针走一步
fast=fast->next->next;//快指针走两步
}
return slow;
}
2.2 链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)
/*
解题思路:
快慢指针法 fast, slow, 首先让fast先走k步,然后fast,slow同时走,fast走到末尾时,slow走到倒数第k个节点。
*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
struct ListNode* slow = pListHead;
struct ListNode* fast = slow;
while(k--)
{
if(fast)
fast = fast->next;
else
return NULL;
}
while(fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
2.3 方法总结
对于特殊节点,我们主要用快慢指针,步数根据题目来灵活判断,走的次数也是灵活判断!
三、判定链表结构
3.1 链表的回文结构_牛客题霸_牛客网 (nowcoder.com)
思路:1.找到中间节点 -> 2.反转中间节点以后的节点 -> 3.双指针一个从头一个从中间节点开始走,遍历链表,遇到不等停止返回false,当中间节点指针遍历到终点,结束返回true。
struct ListNode* middleNode(struct ListNode* head){
struct ListNode*slow=head,*fast=head;
while(fast&&fast->next)
{
slow=slow->next;//慢指针走一步
fast=fast->next->next;//快指针走两步
}
return slow;
}
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* prev = NULL;
struct ListNode* curr = head;
while (curr) {
struct ListNode* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
bool chkPalindrome(ListNode* phead) {
struct ListNode*MiddleNode=middleNode(phead);//找中间结点
MiddleNode=reverseList(MiddleNode);//反转中间结点以后结点
//一个从头走,一个从中间结点走,比较结点val
while(MiddleNode)
{
if(phead->val!=MiddleNode->val)//两者不同则直接返回false
{
return false;
}
else{
//迭代
phead=phead->next;
MiddleNode=MiddleNode->next;
}
}
//全部遍历完返回true
return true;
}
3.2 160. 相交链表 - 力扣(LeetCode)
思路:如果两个相同长度链表同时走,如果能相遇同一结点,则一定有相交节点!
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
int lenA=0,lenB=0;
struct ListNode *curA=headA,*curB=headB;
//分别遍历链表求长度
while(curA)
{
lenA++;
curA=curA->next;
}
while(curB)
{
lenB++;
curB=curB->next;
}
//复原指针
curA=headA;
curB=headB;
//长链表先走差距长度,使两链表长度相同
int Difference=abs(lenA-lenB);
while(Difference--)
{
if(lenA>lenB)
{
curA=curA->next;
}
else{
curB=curB->next;
}
}
//二者同时走,若遇到相同结点则返回该节点,不同继续遍历
while(curA)
{
if(curA==curB)
{
return curA;
}
else{
curA=curA->next;
curB=curB->next;
}
}
//两者都走到空则没有相同结点返回空
return NULL;
}
3.3 141. 环形链表 - 力扣(LeetCode)
//利用物理相对原理,我们可是快指针一次走两步,慢指针一次走一步,两者一定会相遇
//假设慢指针刚进入环时,两者相距N步,将慢指针看作惯性系(看作静止),
//快指针相对于慢指针每次走一步,那么一步步走两者一定会相遇~
bool hasCycle(struct ListNode *head) {
struct ListNode *slow=head;
struct ListNode *fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
return true;
}
}
return false;
}
有人会问如果快指针一次走三步或者更多,两者会相遇吗?
答案是可能会,但有些情况不会相遇!
下面用fast一次走三步举例哪种情况不会相遇
3.4 142. 环形链表 II - 力扣(LeetCode)
struct ListNode * hasCycle(struct ListNode *head) {
struct ListNode *slow=head;
struct ListNode *fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
return slow;
}
}
return NULL;
}
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode *meet=hasCycle(head);
if(meet)
{
while(meet!=head)
{
meet=meet->next;
head=head->next;
}
return meet;
}
return NULL;
}
3.5 方法总结
对于判断链表结构问题,我们主要在研究链表特性上下功夫,找突破口。这类问题相比于前面的问题,更加注重思想,题目的解题方法也不是千篇一律,而是灵活动态。我认为做这类题要多积累特别的思想,多会找突破口。
四、总结
1.对于链表问题,它的节点之间逻辑联系很复杂,有时指针关系很容易令人头脑发热,建议写链表题目能多开辟变量尽量多开辟,以免自己绕糊涂了。
2.建立新链表,记得能带哨兵一定带哨兵,这样可以有效解决head不停的变动带来的麻烦。
如果这篇文章对你有用,希望你给一个三连~~~