#C #链表
Feeling and experiences:
两两交换链表中的节点:力扣题目链接
本题的要求是要两两交换(是在不改变值的条件下),那么首先想到的问题是怎么交换?
两两交换,但如果链表节点为奇数呢?
刚开始看这个题的第一反应,就是想到了昨天做的翻转链表。虽然思路还是不够清晰,但是能确定的一点就是:一定要有临时指针来存储中间变量!
因为链表中的题,一旦涉及到了交换,删除等基本抽象操作,都要有临时变量的存储,不然节点要丢失。
最先想到的思路:既然是两两交换,那么就先模拟两个节点的交换,再推广到所有节点!
清楚了这一个问题后,开始动手画草图,模拟怎么交换:
第一步:先设置一个虚拟头节点,设置一个cur指针初始化指向该虚拟头节点。
注意:cur是指向该虚拟节点的地址,所以第一次cur指向哪里,该虚拟节点就指向哪里(后续cur要变,但虚拟节点就不会再变了)
第二步:以1,2,3,4,5为例,两两交换了就应该是2在头。
所以我们让cur指向2这个节点,因为虚拟节点本来指向1的,现在要指向2,防止1节点后续找不到,所以创建临时指针temp来存储。
前面提到了,第一次虚拟节点的指向,会随cur的改变而改变!
第三步:再让2指向1,就完成2和1的交换。
但此时要注意的是,当2指向1,就会使本来指向3的指针指向了1,那么3指针及其以后的就丢失了,所以我们要用temp02临时指针来存储3这个节点。
第四步:让1指向3(因为3是被临时记录了,所以可以找到)。
最后再跟新cur指针。(因为跟新了cur指针,cur指针的地址就改变了,所以虚拟节点就不会再随着cur改变而改变了。)
那么循环结束条件是什么?
当链表节点数为偶数时,cur指向的下一个节点为空。
当链表节点数为奇数时,两两交换了,剩下的最后一个节点不交换,就是最后一个节点指向的下一个节点为空,也就是cur的指向的节点的下一个节点为空。
以下是整体过程代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* swapPairs(struct ListNode* head) {
//第一步:创建一个虚拟头节点
struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
newNode->next = head;
struct ListNode* cur = newNode;
while(cur->next!=NULL && cur->next->next!=NULL){
//先用两个临时变量记录要改变指向方向的节点,防止后续找不到该节点
struct ListNode* temp = cur->next;
struct ListNode* temp2 = cur->next->next->next;
//第二步:
cur->next = cur->next->next;
//第三步:
cur->next->next = temp;
//第四步:
cur->next->next->next = temp2;
//更新cur
cur = cur->next->next;
}
return newNode->next;
}
代码随想录中的图解:
模拟完这道题后,立刻就明白了可以用递归的方法解答。这就是一个局部推整体的过程!
这里推荐一个B站up的视频,可以帮助理解递归:视频链接
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* swapPairs(struct ListNode* head) {
//递归法:紧紧抓住原来的函数究竟返回的是什么?
//递归终止条件:
if(head == NULL || head->next == NULL){
return head;
}
struct ListNode* temp = head->next;
//接收返回值
head->next = swapPairs(temp->next);
temp->next = head;
return temp;
}
关键就看接收的返回值!
删除链表的倒数第N个节点:力扣题目链接
该题的就非常简单了,思路就是遍历寻找到要删除节点的前一个节点。
就是一个基础的删除节点题目:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* temp = malloc(sizeof(struct ListNode));
temp->next = head;
int length = getLength(head);
struct ListNode* cur = temp;
//循环走到要删除的节点的前一个节点
for(int i = 1;i<=length-n;++i){
cur = cur->next;
}
cur->next = cur->next->next;
return temp->next;
}
int getLength(struct ListNode* head){
int length = 0;
while(head){
++length;
head = head->next;
}
return length;
}
后面再思考一下递归的写法!
链表相交:力扣题目链接
找相交的节点,最开始还有点没读懂这道题是什么意思
思路为,定义两个指针,找到指针相等的点,则为交点。
后面明白了,关键就在于如果指针相等(这不代表值相等),而是地址相同,则代表两个指针指向同一个地址,则说明该点为交点!
示例:
代码随想录中的图解:
代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
while (curA != null) { // 求链表A的长度
lenA++;
curA = curA.next;
}
while (curB != null) { // 求链表B的长度
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
//1. swap (lenA, lenB);
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
//2. swap (curA, curB);
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap-- > 0) {
curA = curA.next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
没有涉及到指针的重要用法,就用Java代码写了。
环形链表II:力扣题目链接
主要问题在于:
是否存在有环?
如果有环,怎么找到那个出口?
使用快慢指针的思想,如果慢指针速度为1,快指针速度为2,那么它们的相对速度为1,则如果存在环,它们一定会相遇。
代码随想录中的图解:
以此图为例,用到一些数学知识:
首先,声明慢指针slow速度为1,快指针fast速度为2。
假设在第二个方块处相遇(环内随机一个位置),此时,slow走过的节点数为x+y。
fast走过的节点数为x+n(z+y),其中n为圈数,大于等于1(因为至少fast要跑一圈才能相遇,slow从环入口进来,速度慢,一圈是追不上的)
而fast速度是slow的两倍,所以走过的节点数也是slow的两倍,综上有等式:
2*(x+y) = x+n(z+y) =》 整理得:x=nz-(n-1)y
而这个x,不就是入口节点的位置嘛!
但是后面还有z,y,还不看不出来什么。
举个特殊例子,当n等于1时,说明fast跑了一圈就和slow相遇了,此时x=z。
我们再结合图来看一下,x=z,那岂不是就意味着相遇点到入口的距离和头节点到入口的距离相等?
那么思路一下就清晰了,当fast == slow 的时候,也就是相遇点。
记录这个相遇点,让两个速度为1的指针,一个从相遇点走,一个从头节点走,它们相遇,就找了入口!
思路代码如下:
/**
* 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;
//找到slow , fast 相遇点
while(fast != NULL && fast->next != NULL) {
//设置它们的速度,fast一下走两节点,slow一下走一个节点
fast = fast->next->next;//代表速度为2
slow = slow->next;//代表速度为1
if(fast == slow){
//找到了相遇点
//则从这个相遇点设置一个指针速度为1,头节点再设置一个指针速度为1,让它们开始运动
struct ListNode* index01 = fast;
struct ListNode* index02 = head;
//当这两个指针相遇了,则该相遇点为入口
while(index01 != index02){
index01 = index01->next;
index02 = index02->next;
}
return index01;
}
}
return NULL;
}
这道题真的非常有意思,夹杂数学思想,太crazy了 !
从今天练的题目总结下来,双指针运用广泛,要熟练掌握。(特别是数组和链表中的题)
唯愿时光清浅,将你温柔以待。
不问开花几许,只愿浅笑安然~
Fighting!