两两交换链表中的节点
-
前提
- 只能交换节点,不能交换值
-
要点
-
添加虚拟头节点
-
画出改链步骤图更清晰,最小单元是cur->target1->target2(最后指向NULL节点也画出来)
-
单指针(cur):按步骤顺序执行,丢失的值用tmp、tmp1存储,单次循环结束时cur移动两个节点。(双指针:单次循环结束时时移动两个指针)
-
/* 判断cur->next->next(奇个数)是否为空前,要先判断cur->next(偶个数)是否为空,防止cur->NULL->next。 * 判断cur->next是否为空则不用判断cur是否为空,因* 为没有跨节点。 */ cur->next != NULL && cur->next->next != NULL
-
当循环体内的操作是cur->next->next->next;时,只需要满足条件cur->next->next != NULL,因为要避免cur->next->NULL->next,cur->next->next->NULL则无所谓。即不管什么操作都要防止NULL->next这种情况。
-
迭代版本
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* swapPairs(struct ListNode* head) {
struct ListNode* fakehead = malloc(sizeof(struct ListNode));
fakehead -> next = head;
struct ListNode* cur = fakehead;
struct ListNode* tmp = NULL;
struct ListNode* tmp1 = NULL;
while (cur->next != NULL && cur->next->next != NULL){
tmp = cur -> next;
tmp1 = cur -> next -> next -> next;
cur -> next = cur ->next -> next;
cur ->next -> next = tmp;
tmp -> next = tmp1;
cur = cur -> next -> next;
}
head = fakehead -> next;
free(fakehead);
return head;
}
!递归版本
- 不添加虚拟节点
- 最小单元是head + newhead,返回新头结点地址给上一级递归,用下一级递归的返回值赋值给旧头结点的next。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* swapPairs(struct ListNode* head) {
while (!head || !head->next){
return head;
}
struct ListNode* newhead = head ->next;
head -> next = swapPairs(newhead -> next);
newhead -> next = head;
return newhead;
}
删除链表的倒数第n个节点
- 要点
- 使用虚拟头节点
- 双指针法
- right先走n+1步,因为left要停留在删除节点的前一个节点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* fakeHead = malloc(sizeof(struct ListNode));
fakeHead -> next = head;
struct ListNode* right = fakeHead;
struct ListNode* left = fakeHead;
int i = 0;
while (i < n && right -> next != NULL){
right = right -> next;
i++;
}
if (i != n){
return -1;
}
while(right -> next != NULL){
right = right -> next;
left = left -> next;
}
left -> next = left -> next -> next;
head = fakeHead -> next;
free(fakeHead);
return head;
}
链表相交
- 前提
- 整个链式结构中不存在环
- 函数返回结果后,链表必须保持其原始结构
- 要点
- 是指针相交,不是值相交(比如两个不相交的等价链表),也就是两个链表连接到同一个节点。
- 两个相交链表的最后节点始终相同。一旦它们相交,之后的所有节点将相等。
- 先将两个链表处理成等长(从后向前算),方便同时移动比较,将时间复杂度降为O(n)。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode* nodeA = headA;
struct ListNode* nodeB = headB;
int lenA = 0;
int lenB = 0;
while(nodeA != NULL){
nodeA = nodeA -> next;
lenA++;
}
nodeA = headA;
while(nodeB != NULL){
nodeB = nodeB -> next;
lenB++;
}
nodeB = headB;
if (lenA > lenB){
int cnt = lenA -lenB;
while (cnt){
nodeA = nodeA -> next;
cnt--;
}
}
else {
int cnt = lenB - lenA;
while (cnt){
nodeB = nodeB -> next;
cnt--;
}
}
while(nodeA != NULL || nodeB != NULL){
if (nodeA == nodeB){
return nodeA;
}
nodeA = nodeA -> next;
nodeB = nodeB -> next;
}
return NULL;
}
环形链表2
- 前提
- 不允许修改链表
- 要点
- 判断链表是否有环:快慢指针法,快指针走两步,慢指针走一步,如果快慢指针相遇则代表这个链表有环。
- 如果有环,怎么找到这个入口:一个指针从头节点出发,一个指针从相遇节点出发,两个指针相遇的地方就是入口节点,详情参照代码随想录
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast != NULL && fast -> next != NULL){
fast = fast -> next -> next;
slow = slow -> next;
if (fast == slow){
slow = head;
while (fast != slow){
fast = fast -> next;
slow = slow -> next;
}
return fast;
}
}
return NULL;
}
总结
- 主要用法有双指针法
- 迭代和递归的基本模型不同
- 注意循环结束条件,既要覆盖目标操作,又要防止NULL->next(有可能出现在循环体逻辑操作也有可能出现在循环判断条件)