24. 两两交换链表中的节点
题目链接:力扣-24. 两两交换链表中的节点
思路:
-
双指针法
这道题目比较简单,利用双指针法就能解决,一开始写的过于复杂,导致最后超时了,超时原因还没找到,可能写的时候有些我没注意到的地方。但具体思路是一致的,后续对代码进行优化,顺利通过编译。做了个图来辅助理解,建立虚拟头节点会更加方便。
双指针法代码:
struct ListNode* swapPairs(struct ListNode* head){
struct ListNode *shead = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode *p, *q;
shead->next = head;
p = shead;
q = shead->next;
while(p && q && q->next){
p->next = q->next;
q->next = p->next->next;
p->next->next = q;
p = q;
q = q->next;
}
return shead->next;
}
-
递归
- 先递推到链表尾节点或者空节点。
- 回归到上一层返回的newhead(后一组交换节点后的前一个元素),用来与前一组两两节点交换后相链接。
- 两节点进行交换。
- 继续往上回归,重复34,最后返回的newhead就是两两交换后的新链表的头结点。
递归法理解起来很抽象,知道了思路,写代码时总有一种无从下手的感觉,画了一张图来辅助理解递归的思想。
等我完全学会了递归的思想,会专门出一期博客来总结递归,学习就是一个扫盲的过程。
递归法代码:
struct ListNode* swapPairs(struct ListNode* head){
//递归结束条件:头节点不存在或头节点的下一个节点不存在
if(!head || !head->next){
return head;
}
struct ListNode *newhead = head->next;
head->next = swapPairs(newhead->next);
newhead->next = head;
return newhead;
}
19.删除链表的倒数第N个节点
思路:
关键在于如何找到删除节点的前一个节点,题目给我们的n是倒数的第n个节点,因此可以利用双指针的方法,让快指针比慢指针多走n步,再让快慢指针同时移动,这样快慢指针始终保持n的距离,当快指针指向末尾(null)时,慢指针刚好走到删除的节点。
注:我们要删除节点需要找到该节点的前一个节点,因此需要快指针比原先多走一步,总共需要先走(n+1)步。
双指针法代码:
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
struct ListNode *shead = (struct ListNode*)malloc(sizeof(struct ListNode));
shead->val = 0;
shead->next = head;
struct ListNode *fast = shead;
struct ListNode *slow = shead;
n++;
while(n-- && fast){
fast = fast->next;
}
while(fast){
slow = slow->next;
fast = fast->next;
}
struct ListNode *p = slow->next;
slow->next = p->next;
free(p);
return shead->next;
}
直接计算链表长度代码:
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
struct ListNode *shead = (struct ListNode*)malloc(sizeof(struct ListNode));
shead->val = 0;
shead->next = head;
struct ListNode *p = shead;
struct ListNode *q = shead;
int length = 0;
while(p->next){
p = p->next;
length++;
}
for(int i = 1; i < length-n+1; i++){
q = q->next;
}
struct ListNode *s = q->next;
q->next = s->next;
free(s);
return shead->next;
}
面试题 02.07. 链表相交
题目链接:力扣-面试题 02.07. 链表相交
思路:
简单来说,就是求两个链表交点节点的指针。 这里的交点不是数值相等,而是指针相等。
先分别求出两条链表的长度,求出差值,将短链表移动到和长链表末尾相对齐的位置。此时就能比较其节点是否相同,若不相同,则将指针都往后移一位,继续比较。
这里需要注意链表都为空的情况,在定义指针时都赋值NULL。
代码:
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode *l = NULL, *s = NULL;
int lengthA = 0, lengthB = 0, gap = 0;
s = headA;
while(s->next){
s = s->next;
lengthA++;
}
s = headB;
while(s->next){
s = s->next;
lengthB++;
}
if(lengthA > lengthB){
l = headA;
s = headB;
gap = lengthA - lengthB;
}
else{
l = headB;
s = headA;
gap = lengthB - lengthA;
}
while(gap--){
l = l->next;
}
while(l){
if(l == s){
return l;
}
l = l->next;
s = s->next;
}
return NULL;
}
看题解还有一种双指针法,还没完全理解,等想明白了再来写思路,链接先贴这,供大家一起学习。https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/solutions/1395092/lian-biao-xiang-jiao-by-leetcode-solutio-2kne/
142.环形链表II
题目链接:力扣-142. 环形链表 II
思路:
难度有点大,看完题解后,果然编程的本质是数学。
还未能完全掌握,二刷的时候需要注意。
解题的关键在于:链表是否有环、有环时如何找到该环的入口。
我们使用两个指针,fast 与 slow,让它们都指向链表的头节点。让slow 每移动一个位置,fast 移动两个位置。如果链表中存在环,那它们必然在环中相遇。
设链表中环外部分的长度为 x,环形入口节点到 fast指针与slow指针相遇节点的长度为y,从相遇节点再到环形入口节点长度为 z。
当两指针相遇时, slow指针走过的长度为: x + y, fast指针走过的长度为:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针。
由fast指针走过的长度 = slow指针走过的长度 * 2可得:
(x + y) * 2 = x + y + n (y + z)
x = n (y + z) - y
x = (n - 1) (y + z) + z
因为 fast指针至少要多走一圈才能与slow指针相遇,因此n一定大于等于1。
当 n=1时, x = z。
定义指针 f 指向相遇节点,指针 h 指向头结点处。
让 f 和 h 同时移动,相遇的地方就是环形入口的节点。
贴上学习的文章:代码随想录
代码:
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode *fast = head, *slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
if(fast == slow){
struct ListNode *f = fast, *h = head;
while(f != h){
f = f->next;
h = h->next;
}
return f;
}
}
return NULL;
}
总结:
今天除了环形链表,其他的题目大部分都有思路。
链表中双指针法与递归法很重要,基本上每道题都能用这两种方法解决,后期需要多巩固。
这两天的练习中,慢慢的开始能动的了手了,也算是一种进步,但是速度比较慢,花费的时间很多,新专题的练习需要更加注重效率。