系列文章目录
【代码随想录】Day4 链表part02 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II
前言
链表的第二部分
24. 两两交换链表中的节点
Source: 题目
Note:以前刷过,但是忘记了,证明还是对于链表掌握不熟练,核心在于理解下图的逻辑
循环的逻辑 以及每次循环内的3个链表操作步骤
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
// 这道题需要用到dummy
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* cur = dummy;
// 注意这道题的逻辑,每次cur操作后续的一对节点翻转
// 那么判断条件就是1.若cur后面存在两个点,则进行翻转 2.若cur当前或下个点不存在则不反转,并且结束循环
// 使用两个临时变量 对于指针的位置进行保存 不然赋值后会丢失初始的指针位置
while (cur->next!=NULL && cur->next->next!=NULL){
ListNode* tmp = cur->next;
ListNode* tmp1 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = tmp;
tmp->next = tmp1;
cur = cur->next->next;
}
return dummy->next;
}
};
递归版本供参考
从后往前一对一对进行翻转,翻转后把新链表部分的头部return回去作为外层递归的链表尾部
如此最外层的newHead即为新链表的头部
//递归版本
ListNode* swapPairs(ListNode* head){
//递归结束条件:头节点不存在或头节点的下一个节点不存在。此时不需要交换,直接返回head
if(!head || !head->next)
return head;
//创建一个节点指针类型保存头结点下一个节点
ListNode *newHead = head->next;
//更改头结点+2位节点后的值,并将头结点的next指针指向这个更改过的list
head->next = swapPairs(newHead->next);
//将新的头结点的next指针指向老的头节点
newHead->next = head;
return newHead;
}
19.删除链表的倒数第N个节点
Source: 题目
Note:比较简单的一道题,得先知道倒数第n个是正数第几个,这是个简单的换算关系
然后问题就转化成了删除第k个元素,
所以引入dummy节点来统一删除头节点和第k个节点(k>1)的问题,
这道题要注意的是len的计算得统一不包含dummy节点,因为会影响到被删除节点的位置
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 先计算节点个数
int len = 0;
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* cur = dummy;
cur = cur->next;
while (cur!=NULL) {
cur = cur->next;
len++;
}
int steps = len - n ;
cur = dummy;
while (steps--) {
cur = cur->next;
}
cur->next = cur->next->next;
return dummy->next;
}
};
C++运行0ms 击败100%
Tips:
面试题 02.07. 链表相交
Source: 题目
Note:题目给定2个链表,要求找到第一个链表相交的位置。
链表相交:指两个链表从某部分指向相同的内存地址,在数值上表现为从某节点开始一直到两链表结束之间的节点值都相同
比如链表A 1->2->3->4->5 链表B 3->4->5 此时3为相交的节点,
但如果链表A为 1->2->3->4->5 链表B为 3->4->4 此时两个链表没有交点
因为虽然3,4两节点的值相同,但是由于A是->5而B是->4导致它们指向了不同的内存地址,所以A和B中指向3节点的指针地址并不同。
那么我们可以想到,要比较两链表是否相交,我们需要先把较长的链表跳过前面的部分,一直到剩下部分和较短链表相同位置在进行比较。
然后我们逐次比较每一对节点的指针是否指向了同一内存地址,如果是,则说明这个点就是交点
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* p1 = headA;
ListNode* p2 = headB;
int len1 = 0;
int len2 = 0;
while (p1 != NULL) {
p1 = p1->next;
len1++;
}
while (p2 != NULL) {
p2 = p2->next;
len2++;
}
// 重置初始位置
p1 = headA;
p2 = headB;
// 让链A长 >= 链B长
if (len1 < len2) {
int tmp = len1;
len1 = len2;
len2 = tmp;
ListNode* tmp2 = headA;
p1 = headB;
p2 = tmp2;
}
// 移动到相同位置
int lendiff = len1 - len2;
while (lendiff--) {
p1 = p1->next;
}
// 开始比较从该位置开始是否有某位置指针位置相等,若指针相等则该位置就是第一个相交位置
// 相交只会发生在链表尾部而且仅当尾部n个数字完全相同时 此时两个链表的尾部才会指向同一片内存
// 试想两个链表如果前部有一部分值相等,但之后数字不同(说明指向了不同的地址),那么他们即使值相同,也不可能是相等的指针
while (p1 != NULL) {
// 比较的是地址(p1 == p2)而不是值(p1->val == p2.val)
if (p1 == p2) {
return p1;
}
p1 = p1->next;
p2 = p2->next;
}
// 如果遍历完p1都没有发现相等指针说明不相交
return NULL;
}
};
Tips:可使用
swap (lenA, lenB);
swap (curA, curB);
替换if (len1 < len2){}内的内容
142.环形链表II
Source: 题目
Note:本题的难点和关键在于数学部分
设置快慢指针第一次相遇后,初始化一个新指针为链表头,然后该指针和慢指针同时一步步前进,当两指针相遇时的点即为环的入口处
详细的数学推导过程见:
https://programmercarl.com/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
if (fast == NULL ||fast->next==NULL) {
return NULL;
}
while (fast!=NULL && fast->next!=NULL) {
fast = fast->next->next;
slow = slow->next;
if (fast == NULL || fast->next==NULL) {
return NULL;
}
if (fast == slow) {
break;
}
}
ListNode* step = head;
while (step!=slow) {
step = step->next;
slow = slow->next;
}
return step;
}
};
slow指针走过的节点数为: x + y,
fast指针走过的节点数:x + y + n (y + z),
n为fast指针在环内走了n圈才遇到slow指针,
(y+z)为 一圈内节点的个数A
我们有:(x + y) * 2 = x + y + n (y + z)
即x = (n - 1) (y + z) + z
当n=1时,x = z
即fast指针在环形里转了一圈之后,遇到slow指针,此时就是我们快慢指针的第一次相遇,也就是说表头到入口的距离正好是相遇点到入口的距离!
那么如果我们设置两个相同速度的指针分别在表头和快慢指针相遇点处,当它们相遇时移动的距离就正好是所求距离!
关键点:
慢指针入环的第一圈内必定和快指针相遇,原因是当慢指针转一圈,快指针必定转两圈,且快指针和满指针的相对速度是1
总结
今天主要加深了链表的理解以及环形链表,虽然以前做过,但是不熟练,今天收获很多。
DAY Finished 撒花~