day04| 24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交,142.环形链表II,总结

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

leetcode题目链接

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

day04| 24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交,142.环形链表II,总结_第1张图片
1.想要操作节点1和节点2,cur一定要在这两个节点的前面。所以终止条件是cur->next != nullptr && cur->next->next != nullptr就很容易理解了。这样也能保证最后如果有奇数节点就可以不用管,因为两个next包含了这种情况。空链表代表偶数的情况。
2.上面这个条件的顺序不能反,不然会空指针异常。
3.另外注意tmp和tmp1是定义在while循环里面的,这里是为了能够在新一轮的循环中直接以cur为基准初始化两个指针的位置。感觉也是小技巧,很好用。

class Solution {
public:
    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->next = cur->next->next
            cur = cur->next->next; 

        }

        return dummyHead->next;
    }
};

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

leetcode题目链接

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

进阶:你能尝试使用一趟扫描实现吗?
day04| 24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交,142.环形链表II,总结_第2张图片

题目看着挺简介,一下子就读懂了,但我想了想没啥思路,因为这个链表里面没有定义size,所以我也不知道倒数第n个节点是属于正数第几个节点。昨天那个链表翻转的题目能够绕过从后往前遍历去解决是因为定义了双指针,但双指针也不能用来计数吧?还是说,需要先遍历一下数一下节点数,再删除节点
哈哈,自己不看资料就写出来了,不过确实不难,就是先遍历再删除节点,只是在昨天删除节点的基础上加了遍历。不过题目说进阶是需要只遍历一次就把节点删除,这个我是真没有思路。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        int count = 0;
        while(cur->next != nullptr){
            cur = cur->next;
            count++;
        }
        int m = count - n;
        int i = 0;
        cur = dummyHead;
        while(cur != nullptr){
            if(i == m){
                cur->next = cur->next->next;
            }
            cur = cur->next;
            i++;
        }
        return dummyHead->next;
    }
};

然后看了一下代码随想录的思路,哇,居然还是可以用双指针做出来,不过这次是快慢指针,快指针先走n步,然后两个指针一起走,当快指针走到结尾的时候就是慢指针走到倒数第n个位置的时候,太巧妙了。
开心的是我就只是看了一下思路,然后马上就把代码编完并一次性通过了,我发现刷前面的题的经验能够帮助后面刷题的,相比于之前还是有很大进步的。
注意:我这里和代码随想录的解答有个不同的地方是,解答里在第二个循环之前会有这么一步“fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点”,然后同时第二个while的终止条件是fast != nullptr。我这里没有这一步,但是我的终止条件是fast->next != nullptr,是达到了一样的效果。不过在写这里的时候,我没考虑到底是哪一步会到n,是不是需要加1减1啥的,我就是打算写好直接运行看结果,不正确再调,只不过刚好正确了。等后面熟练了,估计就会对这个终止条件反应很快了吧,现在还没这种水平。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;

        while(n-- && fast != nullptr){
            fast = fast->next;
        }

        while(fast->next != nullptr){
            fast = fast->next;
            slow = slow->next;
        }

        slow->next = slow->next->next;
        return dummyHead->next;
    }
};

面试题 02.07. 链表相交

leetcode题目链接

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

day04| 24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交,142.环形链表II,总结_第3张图片

拿着这个题没思路,我就想着这不得一个一个地址去对应吗,然后我还不知道怎么返回地址(这里的答案是直接比较变量名就可以),就算返回地址又得需要多少次循环才能对应。
然后看了Carl的题解好像明白了,要从链表短的那条入手,找到长链表中与短链表对应的位置,然后一个个往后比较是否相等。

“求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置”

看了题解思路和swap部分代码后自己写出来的。
要注意这么几点:
1.要保证curA是长的那条,根据遍历后得出的长度,使用swap函数进行置换。(小技巧要学会)
2.最后是返回“return NULL”还是“return curA”应该都是一样的,因为能出最后while循环,curA就肯定已经到了NULL了。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        /*1.求出两条链表各自的长度
        2.将A变成长的那条
        3.求出长度之差
        4.将curA移动到curB的相对位置(在步骤2中保证了A是长的那条)
        5.同时往后一一对比,找到相同的位置直接返回
        */
        int lenA = 0, lenB = 0;
        ListNode* curA = headA;
        ListNode* curB = headB;
        while(curA != nullptr){
            curA = curA->next;
            lenA++;
        }
        while(curB != nullptr){
            curB = curB->next;
            lenB++;
        }
        curA = headA;
        curB = headB;
        if(lenA < lenB){
            swap(lenA, lenB);
            swap(curA, curB);
        }
        int n = lenA - lenB;
        while(n--){
            curA = curA->next;
        }
        while(curA != nullptr){
            if(curA == curB){
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

142.环形链表II

leetcode题目链接

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

为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

day04| 24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交,142.环形链表II,总结_第4张图片

读了一遍题,虽然还没思路,但是还是发现了代码随想录的贴心,leetcode中链表题的规则和Carl讲的规则一样,头节点是0,这让我在读好几个题的时候都能理解题意。

以前见过环形链表找交点的题目,记得就是用快慢指针做。不过现在我还是不会做,就算想着快慢指针也不知道怎么弄。

环形链表题解思路-代码随想录

要理解并记住一个知识(不理解也没关系,记住就行):
1.如果存在环,用快慢指针一定会相遇(快指针每次跨两步,慢指针每次跨一步),可以想象为快指针逐步向慢指针靠近的过程。即,快慢指针重新相遇说明一定存在环
2.相遇点是环形的入口节点。这个是结论,记住就好,证明挺多,不过还好都只是一元一次的等式,并不复杂,反正我现在记住就好(估计我以前看过但是现在忘记的原因就是没去理解原理,然后忘记了哈哈)。

然后自己写了一遍,发现有些想简单了,能通过部分用例。但是没想明白还要考虑什么。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != nullptr){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow){
                return slow->next;
            }
        }
        return NULL;
    }
};

正解如下:
1.先注意一下第一个while中,终止条件有两个,因为fast是跳两步,这种以后遇到都要注意,fast如果条三个就是三个终止条件了。
2.我到现在其实还没明白,为什么还要来一个index1/2,不是说快慢指针相遇就已经是入口了吗,即slow==fast,为什么还要用index去跑一圈。【存疑点,下面有解答】

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;
    }
};

上面存疑点的解答,其实代码随想录写的有,只不过我看的时候忽略了。


再从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同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。


总结

感觉链表这种题目需要在草稿纸上画一下图,用于理清操作步骤,确定要几个指针进行操作。其它的只需要记住代码随想录提示要注意的点就好。
1.虚拟头节点
2.双指针

参考资料

代码随想录

你可能感兴趣的:(刷题,链表,数据结构,leetcode)