1.朴素思想:找到需要交换结点的前一个结点然后利用中间变量进行交换。建议像我一样的初学者不必节省变量,可以多写几行代码增加可读性,核心是统一处理逻辑,便于理解,本题中就使用了虚拟头结点,统一了每次交换的处理。
第一次处理前
第一次交换后
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* swapPairs(struct ListNode* head) {
typedef struct ListNode ListNode;//重定义结构体
ListNode * shead=(ListNode*)malloc(sizeof(ListNode));//创建一个虚拟头结点指向真正的头结点
shead->next=head;
ListNode* cur,*temp,*temp1;//cur表示当前结点,知道了cur的位置才能交换其之后两个结点
cur=shead;//用cur代替虚拟头结点进行交换结点操作,因为最后还需要用虚拟头结点返回新链表头结点
while(cur->next!=NULL&&cur->next->next!=NULL){//循环终止条件分别表示偶数、奇数结点两种情况
temp=cur->next;//交换第一步断开了指针,之后还要用到该结点,所以用一个临时指针保存一下
temp1=cur->next->next->next;
cur->next=temp->next;//具体交换结点代码
cur->next->next=temp;
temp->next=temp1;
cur=cur->next->next;//cur指向下两个需要交换的结点,开始下一轮交换
}
head=shead->next;//用cur代替shead进行交换操作就是为了方便最后返回头结点
free(shead);//释放虚拟头结点
return head;
}
2.递归写法,递归代码简单却不易理解,但核心其实还是交换逻辑,注意理解递归函数实现的功能。(注:可通过2个结点4个结点这样简单的例子模拟递归算法过程、方便理解)
/**
* 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;//表明结点只有0个或1个不用交换,直接返回头结点
struct ListNode* nhead=head->next;//新头结点是头结点的下一个结点
head->next=swapPairs(nhead->next);//交换之后的头结点指向之后完成递归链表的头结点
nhead->next=head;//新头结点成为真正的头结点
return nhead;
}
1.双指针法经典题,设置一快一慢两个指针。删除倒数第N个结点需要找到第倒数N+1个结点才能方便操作,所以选择先让快指针移动N+1步,然后快慢指针同时移动,当快指针遍历结束时慢指针指向的就是倒数第N+1个结点,之后进行常规删除操作。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {//双指针法
struct ListNode* shead=(struct ListNode*)malloc(sizeof(struct ListNode));
shead->next=head;//创建一个虚拟头结点指向当前头结点
struct ListNode* quick=shead;//采用快慢指针法
struct ListNode* slow=shead;
for(int i=0;i<n+1;i++){//要删除倒数第n个结点需要找到第倒数n+1个结点
quick=quick->next;//先让快指针跑n+1步
}
while(quick!=NULL){//当快指针遍历链表结束时,慢指针就指向倒数n+1个结点
quick=quick->next;
slow=slow->next;
}
struct ListNode * temp=slow->next;//中间变量保存需要删除的结点
slow->next=temp->next;//常规的删除操作,记得释放内存空间
free(temp);
head=shead->next;
free(shead);
return head;
}
1.第一反应408的数据结构真题,朴素思想是先把两个链表对齐,然后同步遍历,找到第一个相交结点然后返回,注意有无虚拟头结点代码区别。(注:相交是指指针指向同一片地址空间。)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
int m=length(headA);
int n=length(headB);
for(headA;m>n;m--)//链表对齐
headA=headA->next;
for(headB;n>m;n--)
headB=headB->next;
while(headA!=NULL&&headA!=headB){//调整之后两个链表长度相同
headA=headA->next;//所以headA!=NULL与headB!=NULL等价
headB=headB->next;
}
return headA;
}
int length(struct ListNode* str){//链表长度计算函数
int len=0;
while(str){
len++;
str=str->next;
}
return len;
}
2.之后又去看了官方的另一种解法,非常有意思,很难想到。核心思想是:两个链表同时遍历,遍历完自己然后交替遍历对方。若有相交结点,停止时就是相交结点的起点,若不相交则会一直遍历结束返回NULL。
/**
* 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)//双指针法,A空或B空则不可能相交返回空指针
return NULL;
struct ListNode*PA=headA,*PB=headB;
while(PA!=PB){//核心逻辑:A和B交替遍历,停止时就是相交结点或者空结点
PA=PA==NULL? headB :PA->next;
PB=PB==NULL? headA :PB->next;
}
return PA;
}
1.第一反应是用额外空间记录访问次数,之后进行遍历找到第一个访问第二次的结点。奈何本人水平太差以前只做过用额外数组记录访问次数的问题,不知如何下手。(看了题解之后才发现是用哈希表实现,C语言实现哈希表较复杂,遂放弃……)
2.用数学方法构造模型,然后用快慢双指针实现(也不会……看视频勉强理解,本人水平有限,精力也不支持写出更深刻的理解,有兴趣的伙伴可以去看Carl老师的视频讲解,讲的很棒)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
typedef struct ListNode ListNode;
ListNode* slow=head,*fast=head;//采用快慢双指针
while(fast!=NULL&&fast->next!=NULL){
slow=slow->next;
fast=fast->next->next;
if(slow==fast){//相遇时由数学关系可以得出slow、fast、head距入环结点相等
ListNode* index1=head;
ListNode* index2=slow;
while(index1!=index2){
index1=index1->next;
index2=index2->next;
}
return index1;
}
}
return NULL;//无环则返回NULL
}
今日总结:已经开始恐惧哈希表了。