题目链接/文章讲解/视频讲解: 代码随想录
ListNode* swapPairs(ListNode* head) {
if(head == nullptr || head->next == nullptr)
return head;
ListNode* cur = head->next;
ListNode* newHead = cur;
ListNode* pre = head;
while (true) {
if (cur->next != nullptr && cur->next->next != nullptr) { //如果后续还有结点
ListNode* temp = cur->next;
cur->next = pre;
pre->next = temp->next;
pre = temp;
cur = pre->next;
}
else{ //如果后续没有节点
if (cur->next != nullptr) { //如果后续有一个不需要交换的单独结点
ListNode* temp = cur->next;
cur->next = pre;
pre->next = temp;
}
else { //如果后续没有节点了
cur->next = pre;
pre->next = nullptr;
}
break;
}
}
return newHead;
}
思路:以上是我第一次写出的代码,使用pre和cur记录两个相邻的节点,每次将这两个节点后移;使用temp来记录cur之后的一个节点来更新pre和cur。具体的思路是先将cur的next指向pre,再将pre的next指向原本cur后面两个的节点,这样后面两个节点交换后顺序还是正确的。还有就是通过判断后续是否还有节点来确定交换的逻辑,若后面只剩一个节点了,则就将cur的next指向pre后再将pre的next指向这个后续节点即可;若后面没有节点了,则就将cur的next指向pre后再将pre的next指向这个空指针,这样就可以完成两两交换了。
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向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移动两位,准备下一轮交换
}
return dummyHead->next;
}
看了视频讲解和示例代码,发现我想的复杂且绕弯了,其实只需要使用要翻转的两个节点的前一个节点,就可以实现两两交换了,也可以直接吧pre的next指向原本cur后面的一个节点,这样做思路更加清晰简单,同时也不需要进行最后的边界特例的交换,代码更加简洁明了。
题目链接/文章讲解/视频讲解:代码随想录
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
while(n-- && fast != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
// ListNode *tmp = slow->next; C++释放内存的逻辑
// slow->next = tmp->next;
// delete nth;
return dummyHead->next;
}
示例代码,重点感觉是需要使用虚拟指针,能避免对头结点的讨论, 思路是使用快慢指针,用快指针提前走n+1步,这样快慢指针再同时前进时当快指针指向末尾的空指针时,满指针正好停在倒数第n个节点的前一个节点,就可以直接删除倒数第n个节点了。
题目链接/文章讲解: 代码随想录
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA = 0, lenB = 0;
ListNode* pa = headA;
ListNode* pb = headB;
while(pa || pb){
if(pa){
++lenA;
pa = pa->next;
}
if(pb){
++lenB;
pb = pb->next;
}
}
pa = headA;
pb = headB;
while(lenA > lenB){
pa = pa->next;
--lenA;
}
while(lenA < lenB){
pb = pb->next;
--lenB;
}
while(pa && pb){
if(pa == pb) return pa;
pa = pa->next;
pb = pb->next;
}
return nullptr;
}
思路:先计算链表A和B的长度,再将长的链表走到距结尾和短的距离相同的位置,然后让AB同时前进,若遇到相同的节点了,则说明有重合的部分,返回相同节点即可,若不存在,则返回空指针。
题目链接/文章讲解/视频讲解:代码随想录
快慢指针解决 在第141题,我们提到过快慢指针,先判断是否有环,如果有环,在来找环的入口。我们假设是有环的,那么会有两种情况,我们来画个图看一下
1,环很大
假如他们在相遇点相遇了,那么慢指针走过的距离是a+b,快指针走过的距离就是a+b+c+b,因为相同时间内快指针走的距离是慢指针的2倍,所以有a+b+c+b = 2*(a+b),整理得到a=c,也就是说从相遇点到环的入口和链表的起始点到环的入口,距离是一样的。在相遇点的时候我们可以使用两个指针,一个从相遇点开始,一个从链表头开始,他们每次都走一步,直到他们再次相遇位置,那么这个相遇点就是环的入口。
2,环很小
那么这种情况,快指针在环上转了好几圈了,慢指针才走到环上,假如快指针在环上已经走了m圈了,慢指针在环上走了n圈,他们最终在环上相遇
那么慢指针走过的距离是:a+b+n(b+c) (b+c其实就是环的长度) 快指针走过的距离是:a+b+m(b+c) 在相同的时间内快指针走过的距离是慢指针的2倍,所以有
a+b+m(b+c) = 2(a+b+n*(b+c)) 整理得到
a+b=(m-2n)(b+c), 上面b+c其实是环的长度,也就是说a+b等于(m-2n)个环的长度,这个时候我们还可以使用两个指针一个从相遇点开始,一个从链表头开始,这时候就会出现一个现象就是一个指针在链表上走,一个指针在环上转圈,最终会走到第1种情况,就是环很小(我们可以认为链表前面减去m-2n-1个环的长度就是第一种情况了)
搞懂了上面的分析过程,我们来看下代码
ListNode *detectCycle(ListNode *head) {
if (head == NULL)
return NULL;
ListNode *slow = head;
ListNode *fast = head;
while (fast -> next != NULL && fast -> next -> next != NULL) {
fast = fast -> next -> next;
slow = slow -> next;
if (fast == slow) break;
}
//判断是否有环
if(fast->next==NULL||fast->next->next==NULL)return NULL;
//有环则将fast移动至head并移动S2距离
fast=head;
while(fast!=slow){
slow=slow->next;
fast=fast->next;
}
return fast;
}