刷题记录Day4-链表(两两交换链表中的节点、删除链表的倒数第N个节点、链表相交、环形链表)

刷题记录Day4-链表(两两交换链表中的节点、删除链表的倒数第N个节点、链表相交、环形链表)



前言

题目来源:leetcode
刷题顺序:代码随想录
刷题工具:VSCode+leetcode插件
补充:会结合LeetCode 101: A LeetCode Grinding Guide (C++ Version)相似题目一起做。


一、两两交换链表中的节点

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

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

示例:
图片来源于leetcode-24

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

代码:

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyhead = new ListNode(0,head);
        ListNode* cur = dummyhead;
        while(cur->next !=nullptr && cur->next->next !=nullptr){
        	//例如:dh->1->2->3->4
        	//tmp1用来保存1,tmp2用来保存3
            ListNode* tmp1 = cur->next; 
            ListNode* tmp2 = cur->next->next->next;
            cur->next =cur->next->next; 
            cur->next->next = tmp1;
            cur->next->next->next = tmp2;
            cur = cur->next->next; //每次移动两格
        }
        return dummyhead->next;
    
    }
};

二、删除链表的倒数第N个节点

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

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

示例:
图片片来源于leetcode-19

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

代码:
方法一,最开始写的版本,一次ac

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyhead = new ListNode(0,head);
        ListNode* cnt = dummyhead;
        int sz = 0;
        while(cnt->next != nullptr){
            ++sz;
            cnt = cnt->next;
        }
        ListNode* cur = dummyhead;
        for(int i =0; i<sz-n; ++i){
            cur = cur->next;
        }
        ListNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        tmp = nullptr;
        return dummyhead->next;
    }
};

方法二:
进阶问题是一次遍历,所以想到第二个双指针的办法。
没看题解就想出来而且两次就ac了!牛牛牛!而且只用写一个while循环!
需要注意第一次为什么没ac,因为cur需要移动到要删除节点的前一个节点,所以要把n+1,这样cur会少移动一次从而到达需要删除节点的前一个位置!

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyhead = new ListNode(0,head);
        ListNode* cnt = dummyhead;
        ListNode* cur = dummyhead;
        int sz = 0;
        while (cnt->next != nullptr){
            cnt = cnt->next;
            ++sz;
            if(sz == n+1){
                cur = cur->next;
                --sz;
            }
        }
        ListNode* tmp =cur->next;
        cur->next = cur->next->next;
        delete tmp;
        return dummyhead->next;
    }
};

三、 链表相交

1. 160链表相交

题目:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

示例:
图片来源于leetcode-160

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。

代码:
方法一:暴力解法。蠢的很,但我没想到其他解法。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* dummyheadA = new ListNode();
        dummyheadA->next=headA;
        ListNode* dummyheadB = new ListNode();
        dummyheadB->next=headB;
        ListNode* curA = dummyheadA;
        while (curA->next != NULL){
            ListNode* curB = dummyheadB;
            while (curB->next != NULL){
                if(curB->next == curA->next){
                    return curB->next;
                }
                curB = curB->next;
            }
            curA = curA->next;
        }
        return NULL;
    }
};

方法二:
双指针,没想到还可以这样。看题解看到的。
让两个指针走完各自的路后再走对方的路。
两者的路程是a+c+b = b+c+a,所以如果有交点肯定会在交点相遇。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        while (curA != curB){
            curA = curA->next;
            curB = curB->next;
            if(curA == NULL && curB == NULL){
                return NULL;
            }
            if(curA == NULL){
                curA = headB;
            }
            if(curB == NULL){
                curB = headA;
            } 
        }
        return curA;
    }
};

方法三:
卡哥的方法,长-短得到一段距离,让长的链表先移动这段距离,然后两个指针同时移动。这个方法也不错。就是算链表长度要把两个链表都先遍历一遍。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int sizeA=0, sizeB = 0;
        while (curA != NULL){
            ++sizeA;
            curA = curA->next;
        }
        while (curB != NULL){
            ++sizeB;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        if(sizeB>sizeA){
            swap(sizeA,sizeB);
            swap(curA,curB);
        }
        int gap = sizeA-sizeB;
        while(gap--){
            curA = curA->next;
        }
        while(curA != NULL){
            if(curA == curB){
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

四、环形链表

1. 142环形链表II

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

示例:
图片来源于leetcode-142

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

代码:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        //这题在leetcode101做过,试试能不能直接ac。
        //用双指针思想,一快一慢。
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast!= NULL && fast->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
            if(slow == fast){
                break;
            }
        }
        if(fast == NULL || fast->next == NULL) return NULL;
        fast = head;
        while (fast != slow){
            fast = fast->next;
            slow = slow->next;
        }
        return fast;
        
    }
};

思考:
图片来源于代码随想录
慢指针slow的路程:slow = x+y+k(y+z)
快指针fast的路程: fast = x+y+n(y+z)
那么x+y+n(y+z) = 2(x+y+k(y+z))
=> x= (n-2k-1)(y+z)+z
特殊情况下,比如k=0(慢指针一圈都没走就被追上),n=1(快指针走了一圈就追上慢指针)。那么x=z,直观的发现,如果在快慢指针相遇的时候有一个指针3从头节点出发,这个指针3会和慢指针在环形入口的地方相遇。
推导出普遍情况:快慢指针相遇时,一个指针3从头节点出发,慢指针走了n-2k-1圈后会和指针3在环形入口节点处相遇。

总结

链表还是蛮有意思的,和数组疑难点不一样的地方是,链表要时时考虑节点是否是空的情况,或者是否进入死循环。总结一下:
1.ListNode* xx = new ListNode();和ListNode* xx = node;
前者相当于新建了一个节点,后者是一个指针用来指向节点(多用于用来遍历链表或者暂存节点。)
2.很多链表问题通过引入一个虚拟节点dummyhead,会轻松很多,减少头节点前面的问题。
3.160和142的数学思想很有意思,可以常复习复习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值