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

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

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

示例 1:

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

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

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

思路:代码随想录算法训练营第四天| 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 02.07. 链表相交 142.环形链表II_第2张图片

这里要注意,这三个步骤在换的过程中,链表是一直在变化的(传了好多次都没办法把图片正过来,放弃了)

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

链表的变换按照图中的过程改变。这是这道题最绕弯子的地方,其他还好。

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

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

示例 1:

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

输入: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个,然后再反转回去,顺便返回头节点。非常麻烦,并且不知道没考虑到什么条件,没运行成功(可以后面再看一下是哪里的情况没有考虑周全)

较为简洁的思路是,定义快指针和慢指针,通过控制快指针和慢指针之间的间隔,删除相应的节点,最后直接返回dummyHead的下一个节点

  • 定义fast指针和slow指针,初始值为虚拟头结点,如图:

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

  • fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图: 

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

  • fast和slow同时移动,直到fast指向末尾,如题: 

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

  • 删除slow指向的下一个节点,如图: 

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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
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;
        while (fast != NULL){
            fast = fast -> next;
            slow = slow -> next;
        }
        slow->next = slow->next->next; 
        return dummyHead->next;
    }
};

这里的while循环里,while(n--&&fast!=null)有没有fast!=null都可以运行成功,没啥影响。

 02.07. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

题目数据 保证 整个链式结构中不存在环。

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

示例 1:

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

输入: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 个节点。

示例 2:

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

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

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

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

思路:本题的思路是遍历两个链表,得到它们的长度后,求出长度的差值gap,然后将较长的链表,先移动gap,再进行比较(这道题写的比较顺利一点)

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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* cruA = headA;
        ListNode* cruB = headB;
        int countA = 0;
        int countB = 0;
        while(cruA != NULL){
            cruA = cruA->next;
            countA++;
        }
        while(cruB != NULL){
            cruB = cruB->next;
            countB++;
        }
        cruA = headA;
        cruB = headB;
        if(countA > countB){
            int gap = countA-countB;
            while(gap--){
                cruA = cruA -> next;
            }
            while(cruA != NULL){
                if(cruA == cruB){
                    return cruA;
                }
                cruA = cruA -> next;
                cruB = cruB -> next;
            }
        }
        else{
            int gap = countB-countA;
            while(gap--){
                cruB = cruB -> next;
            }
            while(cruA != NULL){
                if(cruA == cruB){
                    return cruA;
                }
                cruA = cruA -> next;
                cruB = cruB -> next;
            }
        }
        return NULL;
    }
};

 142.环形链表II

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

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

不允许修改 链表。

示例 1:

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

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

示例 2:

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

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

示例 3:

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

本题的难点一个是在于,如何理解如果有环,快指针和慢指针一定会相遇。定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

还有一个难点是找到环的入口,方法是

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:

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

那么相遇时: 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的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当 n为1的时候,公式就化解为 x = z

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
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;
            if (slow == fast) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2;
            }
        }
        return NULL;
    }
};

你可能感兴趣的:(算法,链表,数据结构)