24. 两两交换链表中的节点
题目:
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
思路:
根据图示顺序重新指向箭头(画图分析时不要忘记将dummyhead画出)其中需要注意由于“1”和“3”要断开前面的箭头,意味着位置丢失,需要先将它们赋值给temp,再进行赋值。同时注意退出while循环的条件,需要判断“cur->next" 和 "cur->next->next"是否为空,且有顺序要求。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyhead = new ListNode();
dummyhead->next = head;
ListNode* cur = dummyhead;
ListNode* temp;
while(cur->next != nullptr && cur->next->next != nullptr) {
//循环条件不要忘了cur->next != null; 且需先访问。
temp = cur->next;
ListNode* temp1 = cur->next->next->next;
//千万不要忘记,只要断开节点,就需要赋值给temp。
cur->next = cur->next->next;
cur->next->next = temp;
cur->next->next->next = temp1;
cur = cur->next->next;
}
return dummyhead->next;
}
};
19.删除链表的倒数第N个节点
题目:
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
思路:
自己做题时的思路,先遍历链表,找到链表长度,然后求出倒数第n个是正着数的第几个,然后删除就可以。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode* cur = dummyhead;
int size = 0; // 初始化为0
while (cur->next != NULL) {
cur = cur->next;
size++;
}
ListNode* cur1 = dummyhead;
int j = size - n;
while (j--) {
cur1 = cur1->next;
}
ListNode* temp = cur1->next;
cur1->next = cur1->next->next;
delete temp;
return dummyhead->next; // 返回删除节点后的链表头节点
}
};
双指针法
思路:
如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* slow = dummyHead;
ListNode* fast = dummyHead;
while(n-- && fast != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
ListNode *tmp = slow->next; //C++释放内存的逻辑
slow->next = tmp->next;
delete tmp;
return dummyHead->next;
}
};
两种方法其实差不了太多,只是双指针法更巧妙一点,同时需要注意fast需要提前走一步。
160. 链表相交
题目:
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
思路:
可以承接前一道题目的双指针思路,首先求出两个链表的长度,然后让长的那个链表的指针先走,直到两个链表的指针后面的结点数量相同,然后再同时后移并对比两个指针是否相同。
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) {
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;
}
};
142.环形链表II
题目:
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
思路:
首先判断链表是否有环
使用快慢指针法,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以如果有环fast一定可以和slow重合。
如果有环,如何找到这个环的入口
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
那么相遇时: 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(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
若n=1,有x = z,
意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
class Solution {
public:
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;
}
};
总结
链表也要合理使用双指针法,可以大大简化算法,环形链表这个太巧妙了,值得反复回顾。