代码随想录算法训练营第四天 | 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II

今天是链表章节最后一天,加油

24. 两两交换链表中的节点

题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:
输入:head = []
输出:[]

示例 3:
输入:head = [1]
输出:[1]


思路:本题需要借助几个临时节点完成交换,想要两辆交换节点,必须有临时节点保存第三个节点。假设前三个节点A、B、C,要交换AB节点的位置,需要新建一个虚拟头节点,并用指针curr指向虚拟头节点,用临时节点temp记录节点A,临时节点1temp1记录节点C,交换代码就是curr->next = curr->next->next; curr->next->next = temp; curr->next->next->next = temp1;也就是head->next = B; B->next = A; A->next = C;(交换代码的含义,不能直接用这个,注意链表节点交换时,要提前保存next指针指向的下一个节点,不然容易丢失下一个节点)

C++版本

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode *head_ = new ListNode(0, head);
        ListNode *curr = head_;
        while (curr->next != 0 && curr->next->next != 0) { // 顺序不能颠倒,不然会出现空指针异常
            ListNode *temp = curr->next;
            ListNode *temp1 = curr->next->next->next;
			
			// 主要交换代码
            curr->next = curr->next->next;
            curr->next->next = temp;
            curr->next->next->next = temp1;

            curr = curr->next->next;
        }
        return head_->next;
    }
};

这题不算难,就是要注意一些细节的问题。


19.删除链表的倒数第N个节点

题目:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:
输入:head = [1], n = 1
输出:[]

示例 3:
输入:head = [1,2], n = 1
输出:[1]


思路:注意是删除倒数第n个节点,不要看错题目要求了!这题我写了两种方法,一种是直接法,首先遍历一遍链表算出链表长度以及倒数第n个节点属于正数第几个节点,然后再遍历一次找到要删除的节点的前一个节点,最后进行删除操作;另一种是利用快慢指针,双指针的方法实在是太妙了,令快慢指针都指向虚拟头节点,快指针先走n步,然后快慢指针再同时往前走,当快指针走到链表的最后一个节点时,慢指针正好在倒数第n+1个节点,便可进行删除操作。

C++版直接方法

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {  //删除倒数第n个
        ListNode *head_ = new ListNode(0, head);  // 创建一个虚拟头节点
        ListNode *curr = head_;
        ListNode *temp = head_;
        int l = 0; int t;  // 
        while (temp != NULL) {  // 计算链表长度(从1开始)
            temp = temp->next;
            l++;
            t = l - n;  // 计算循环次数
        }
        while (--t) {
            curr = curr->next;  // 遍历到需要删掉的节点的上一个节点
        }
        curr->next = curr->next->next;
        return head_->next;
    }
};

C++版双指针

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *head_ = new ListNode(0, head);
        ListNode *fast = head_;
        ListNode *slow = head_;
        while (n-- && fast != NULL) {
            fast = fast->next;  // fast先走n步
        }
        while (fast->next != NULL) {
            fast = fast->next;  // 当fast走到最后一个节点时
            slow = slow->next;  // slow走到倒数第n个节点的前一个节点?
        }
        slow->next = slow->next->next;
        return head_->next;
    }
};

一定要掌握双指针方法的核心思想,并学会灵活应用!太好用了!


面试题 02.07. 链表相交

题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
代码随想录算法训练营第四天 | 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II_第1张图片
题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

示例 1:
代码随想录算法训练营第四天 | 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II_第2张图片
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’

解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。


思路:注意不是数值相等算相交,是节点一样才算相交,这题我一开始没有什么思路,看了Carl哥的视频茅塞顿开!首先要明确,从相交的节点开始,后面的节点都是一样的,直到结束。这可不是一句废话,这是解题的关键。所以将两个链表末尾对齐,从后半部分找重合的节点即可。

C++版本

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *head_A = new ListNode(0, headA);
        ListNode *head_B = new ListNode(0, headB);
        ListNode *curr_A = head_A;
        ListNode *curr_B = head_B;
        int len_A = 0; int len_B = 0;
        while (curr_A->next != 0) {  // 计算链表A的长度
            len_A++;
            curr_A = curr_A->next;
        }
        while (curr_B->next != 0) {  // 计算链表B的长度
            len_B++;
            curr_B = curr_B->next;
        }
        curr_A = head_A;
        curr_B = head_B;
        if (len_B > len_A) {  // 保证最长的是链表A
            swap (curr_A, curr_B);
            swap (len_A, len_B);
        }
        int t = len_A - len_B;  // 将两链表末尾对齐,curr_A需要移动的次数
        while (t--) {
            curr_A = curr_A->next;
        }
        while (curr_A !=NULL) {
            if (curr_A == curr_B) {
                return curr_A;  // 一个一个的返回重合的节点
            }
            curr_A = curr_A->next;
            curr_B = curr_B->next;
        }
        return NULL;
    }
};

这里的一个小tips是,将长链表放在curr_A,将短链表放在curr_B,方便了后续的操作。


142.环形链表II

题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。


思路:不得不感叹双指针是真好用。本题需要一定的数学证明,详情请见代码随想录。本题第一步判断是否有环,第二步找环的入口。第一步:快指针一次走两步,慢指针一次走一步,当两指针相遇说明一定有环。第二步:令快指针起点为相遇点,慢指针起点为头节点,两指针每次都只走一步,下次相遇一定是在环的入口。具体证明见上述链接。

C++版本

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *fast = head;
        ListNode *slow = head;
        while (fast != 0 && fast->next != 0) {  // 快指针走两步,慢指针走一步
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) {  // 快慢指针相遇,不能直接break,不然无法判断无环的情况
                ListNode *index_f = fast;  // 重新赋值index_f指针指向相遇点
                ListNode *index_h = head;  // 另一个index_h指针指向头节点
                while (index_f != index_h) {  // 再次相遇的点就是环的入口
                    index_f = index_f->next;
                    index_h = index_h->next;
                }
                return index_f;
            }
        } 
        return NULL;  // 遍历结束快慢指针都没有相遇过,就没有环
    }
};

其实本题也可以采用 19.删除链表的倒数第N个节点 类似的思路,在找到相遇点后,让快指针绕环走一圈,计算出环的长度L;再将快慢指针重新指向头节点,这次让快指针先走L步,慢指针再和快指针一起移动,每次移动一步;因为快指针比慢指针多走了一个环的距离,所以下一次相遇一定是在环的入口!

这是训练营小伙伴 芒果冰冰 的解法,大家都很棒!像大家学习,加油

你可能感兴趣的:(代码随想录,链表,c++,代码随想录,leetcode)