一、分隔链表
给你一个链表的头节点 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来源:力扣(LeetCode)
链接:分隔链表
分析题目:
将比给定值x小的节点放在等于x或者比x大的节点后面,并且顺序还不能乱。
那我们要创建两个链,一个存放比x小的节点,另一条存放比x大的和等于x的
效果图:
之后我们把存放小节点的链链接存放大节点的链,使大节点的尾节点指向NULL,全部ok
我的蠢逼方法(为了以后回头看自己写的博客,感受自己当年的愚蠢):
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode* partition(struct ListNode* head, int x) { if(head == NULL) { return NULL; } struct ListNode* lessHead = NULL; struct ListNode* lessTail = NULL; struct ListNode* greaterHead = NULL; struct ListNode* greaterTail = NULL; while(head != NULL) { struct ListNode* cur = head; head = head->next; if(cur->val < x) { if(lessHead == NULL) { lessHead = cur; lessTail = cur; } else { lessTail->next = cur; cur->next = NULL; lessTail = cur; } } if(cur->val >= x) { if(greaterHead == NULL) { greaterHead = cur; greaterTail = cur; greaterTail->next = NULL; } else { greaterTail->next = cur; cur->next = NULL; greaterTail = cur; } } } if(lessHead != NULL) { lessTail->next = greaterHead; return lessHead; } else { return greaterHead; } }
完全就是提交完看哪块不完善,就修补修补
高级方法:
ListNode* partition(ListNode* head, int x) { //开一个哨兵位头节点,方便尾插 struct ListNode* lessHead,*lessTail,*greaterHead,*greaterTail; lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode)); lessTail->next = NULL; greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode)); greaterTail->next = NULL; struct ListNode* cur = head; while(cur) { //判断 if(cur->val < x) { lessTail->next = cur; lessTail = cur; } else { greaterTail->next = cur; greaterTail = cur; } //cur向后走一位 cur = cur->next; } //因为使哨兵位,greaterHead的下一个节点才是存放的第一个存放的节点 lessTail->next = greaterHead->next; //将尾节点指向NULL greaterTail->next = NULL; //创建一个newHead保存头节点位置,之后释放开辟的两个节点 struct ListNode* newHead = lessHead->next; free(lessHead); free(greaterHead); return newHead; }
这其中用到了哨兵位,给一个节点开辟空间,之后指向要存放的节点,
这样的好处可以省略第一次存放的时候新链表中的节点为NULL,要将他赋值为要存放的节点
但记得最后要释放它
二、链表的回文结构
链接:链表的回文结构
思路💡💡💡:
说白了就是判断一个链表是不是中心对称,是的话返回true,不是的话返回false
如果是数组的话这题就十分简单了,找到最左边和最右边,左指针往右走,右指针往左走
同时往中间靠拢,依次对比直到在同一个位置,或者左指针大于右指针
但是链表有单向性,不能找到上一个节点
其实这道题就是我们之前做过的链表找中间节点,和链表前插的问题结合到一起
想找到中间节点mid,之后将中间节点后的节点前插到一个新节点称为newHead
之后将mid的下一个节点赋值给newHead,再将mid的next指向NULL,这样链表就被分割为一半了
可以定义一个cur作为给定链表的头部,之后cur与newHead一一对比,不相同就返回false,相同返回true
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) {} };*/ class PalindromeList { public: bool chkPalindrome(ListNode* A) { ListNode* slow = A; ListNode* fast = A; ListNode* newnode = A; ListNode* newhead = NULL; //找中间点 while(fast == NULL && fast->next == NULL) { slow = slow->next; fast = (fast->next)->next; } newhead = slow->next; //逆置 while(newhead != NULL ) { if(newnode == NULL) { newnode = newhead; newhead = newhead->next; } else{ newnode->next = newhead; newnode = newhead; newhead = newhead->next; } } //判断 while(newnode && A) { if(A->val != newnode->val) { return false; } A = A->next; newnode = newnode->next; } return true; } };
三、相交链表
这道题其实问了两个问题❓❓❓:
他俩是否相交?
相交节点在哪?
思路💡💡💡:
1、尾节点相同就是相交,否则就不相交
2、求交点:长的链表先走(长度差)步,在同时走,第一个相同的就是交点
代码🖋🖋🖋:
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { struct ListNode* tailA = headA; struct ListNode* tailB = headB; int lenA = 1; int lenB = 1; //计算各自长度 while(tailA) { tailA = tailA->next; ++lenA; } while(tailB) { tailB=tailB->next; ++lenB; } //不相交 if(tailA != tailB) { return NULL; } //让长的先走差的步数 int gap = abs(lenA-lenB); //看谁长 struct ListNode* longList = headA; struct ListNode* shortList = headB; if(lenA < lenB) { shortList = headA; longList = headB; } while(gap--) { longList = longList->next; } while(longList != shortList) { longList = longList->next; shortList = shortList->next; } return shortList; }
哥们自己写的暴力破解:
思路💡💡💡:
依次取A链表中的每个节点跟B链表中的所有节点比较
如果有地址相同的节点,就是相交,第一个相同的交点
代码🖋🖋🖋:
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { if(headA==NULL || headB==NULL) { return NULL; } struct ListNode* curA = headA; struct ListNode* curB = headB; for(curA;curA != NULL;curA = curA->next) { curB = headB; while(curB) { if(curB == curA) { return curA; } curB = curB->next; } } return NULL; }
暴力破解的时间复杂度为O(N2),第一种方法的时间复杂度为O(N)
为了追求更高效的运行,大家还是学习一下第一种方法
四、环形链表
判断是否是环链表操作起来很简单,但是不一定能想到
假如两个人一个跑的快一个跑的慢,从一条直道出发,跑进一个圆形跑道,那么他们两个总会相遇
所以我们创建一个快慢指针
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ bool hasCycle(struct ListNode *head) { struct ListNode* slow = head; struct ListNode* fast = head; struct ListNode* cur = head; while(fast && fast->next) { slow = slow->next; fast = fast->next->next; if(slow == fast) { return true; } } return false; }
延伸问题:
1、为什么slow和fast一定会在环中相遇?会不会在环里面错过,永远遇不上
结论:他们一定会相遇
分析证明:
第一步:无论怎么走slow和fast,fast一定是先进环,且slow走了入环前的进程的一半
第二步:随着slow进环,fast已经在环里面走了一段了,走了多少跟环的大小有关系
假设slow进环的时候,slow跟fast的距离是N,fast开始追slow
slow每往前走1步,fast往前走2步,判断一下相遇
每追一次,fast和slow的距离变化:
如果N是偶数
N N-1 N-2 N-3 ..... 0
每追一次他们的距离减少1,他们的距离减到0的时候就是相遇点
2、为什么slow走一步,fast走的两步呢?能不能fast走两次以上呢
结论:fast一次走n步?n > 2不一定会相遇
假设:
slow一次走1步,fast一次走3步
slow进环以后,fast跟slow之间的距离为N,fast开始追slow
如果N是偶数
N N-2 N-4 N-6 ..... 0 可以追上
如果N是奇数
N N-2 N-4 N-6 ..... -1 这一次追不上
如果N是奇数,距离变成-1意味着什么呢?意味他们之间的距离变成C-1(C是环的长度)
如果C-1是奇数,那么就永远追不上了,如果C-1是偶数,那么就可以追上
五、环形链表 II
这题与上一道题一样,但是需要返回环的头节点
慢指针走的距离:L+X
快指针走的距离:L+N*C+X (N>=1)
快指针走的路程是慢指针的两倍:
2L + 2X = L + N * C + X
L = N*C + X -> L = (N-1)*C + C - X
入环后,每走C的长度,都会从环的起点开始,所以(N-1)*C忽略不算,推算出L = C - X
那么我们计算出X的位置,之后head和X同时走,再次相遇就是环的起始节点了
/** * 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) { slow = slow->next; fast = fast->next->next; if(slow == fast) { //相遇 struct ListNode* meet = slow; //公式证明 while(meet != head) { meet = meet->next; head = head->next; } return meet; } } return NULL; }
六、复制带随机指针的链表
这道题可是上强度了,非常考验咱们对链表的增删改查
向上图一样我们把链表复制下来很简单,但是节点中的random指向要求与原链表一样,这可就难了
我们将每一个节点后插入一个与前节点相同的节点,插入完毕后,形成一个新链表
假如说要让复制值为7的这个节点中的random值等于被复制节点,
那么只需要node7->random = node7->next->random
将每一个节点都处理ok了之后,再将复制的链链接起来,形成复制的链表
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
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;
cur->next = copy;
cur = copy->next;
}
//2、根据原节点,处理copy节点的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 = copy;
}
cur->next = next;
cur = next;
}
return copyHead;
}