链表part02
今天是8月3日,学习了链表的第二部分。
- 交换链表两个节点,考察对next的操作和tmp的灵活运用。
- 删除链表的倒数第N个节点,双指针减少遍历次数。
- 链表相交,移动链表尾对齐,其实就是动长链表的指针。
- 环形链表,记住方法。
4. 24交换链表两个节点
题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
交换节点比上一道更明显,需要更改原始列表中的节点的next。那么需要合理设置指针+更换next的顺序+及时存储tmp
- 注意指针指的位置:比如节点1->2->3,那么cur指向1,可以操作2和3的next。
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head;
ListNode* cur = dummyHead;
while(cur->next != nullptr && cur->next->next != nullptr) {
ListNode* tmp = cur->next; // 记录临时节点
ListNode* tmp1 = cur->next->next->next; // 记录临时节点
cur->next = cur->next->next; // 步骤一
cur->next->next = tmp; // 步骤二
cur->next->next->next = tmp1; // 步骤三
cur = cur->next->next; // cur移动两位,准备下一轮交换
}
ListNode* result = dummyHead->next;
delete dummyHead;
return result;
}
5. 19删除链表的倒数第N个节点(双指针)
题目:删除链表的倒数第 n
个结点,并且返回链表的头结点。
链表只可以从前往后挨个遍历,想要一遍解决问题常用双指针。删除倒数第n个,只需要让fast早走n步,然后fast和slow同步走,fast走到末尾时,slow->next就是要被删除的节点。
要删除某个点,slow需要指着这个点的上一个节点,才可以做到删除。
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 虚拟头节点
ListNode* dummyhead = new ListNode(0);
dummyhead->next=head;
//快慢指针
ListNode* fast = dummyhead;
ListNode* slow = dummyhead;
//先动fast指针
while(fast->next!=nullptr){//保证动fast时不出界
if(n>0){
n--;
fast=fast->next;
}
else
break;
}
//fast和slow同时动,直到fast到达最后一个节点停止
while(fast->next!=nullptr){
fast=fast->next;
slow=slow->next;
}
// ListNode *tmp = slow->next; C++释放内存的逻辑
// slow->next = tmp->next;
// delete tmp;
slow->next=slow->next->next;
return dummyhead->next;
}
6. 链表相交(移动链表尾对齐)
题目:两个单链表的头节点 headA
和 headB
,找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
求两个链表交点节点的指针。最普通的想法,两个for嵌套,但是考虑相交的特点,相交点后面的都一样,所以:
- 求出两个链表的长度,并求出两个链表长度的差值,然后让两个链表末尾对齐的位置。
- 此时比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到
curA == curB
,则找到交点。 - 否则循环退出返回空指针。
- 交点不是数值相等,而是指针相等。
- 链表尾对齐,其实就是curA移动到中间,剩下的A链表和B一样长。
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) {
swap (lenA, lenB);
swap (curA, curB);
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap--) {
curA = curA->next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != NULL) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
7. 142环形链表
题目:给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
a. 如何判定是否有环?
快慢指针法,定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
- fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇。
- 相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
b. 如何判断环的起始点?
那么相遇时: slow指针走过的节点数为: x + y
, fast指针走过的节点数:x + y + n (y + z)
,n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y = (n - 1) A + z
因此,发现从相遇点到入环点的距离z加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离x。
因此,当发现 slow 与 fast 相遇时,再额外使用一个指针 ptr
。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
// 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2; // 返回环的入口
}
}
return NULL;
}
今日古诗
西江月·日日深杯酒满
朱敦儒〔宋代〕日日深杯酒满,朝朝小圃花开。自歌自舞自开怀,无拘无束无碍。
青史几番春梦,黄泉多少奇才。不须计较与安排,领取而今现在。
上片起首写出词人终日醉饮花前的生活。深杯酒满见得饮兴之酣畅,小圃花开点出居处之雅致。无一字及人,而人的精神风貌已隐然可见。后二句则直陈快乐自由的情态;
下片文情陡变,两个对句表达了词人对世事人生的认识,末句是对上片所描述闲逸自得生活之底蕴的概括和揭示。全词用语浅自而意味悠远,清新淡雅,韵味天成,语意俱佳,流露出一种闲旷的情调