代码随想录第四天:两两交换结点、删除倒数的元素、两条链表的公共部分、环形链表

1.两两交换结点

题目链接:. - 力扣(LeetCode)

讲解链接:代码随想录 (programmercarl.com)

视频讲解:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点_哔哩哔哩_bilibili

本题注意处理交换结点前后结点的关系,即前结点的前结点和后结点的后结点,每次交换完两个节点后,将所有工具指针向后移动两位即可。

而中止循环的时机是前结点存在,后结点不存在,或者二者都不存在。所以仅需判断后指针所指是否为空即可。

再来思考需要多少工具指针完成一次交换:前结点和后结点的指针,因为要前前结点指向后结点,所以需要pre指针指向前前结点,然后后结点指向前结点,前结点指向后后结点。而此时后结点的next已经不是后后结点了,所以还需要一个指向后后结点的指针,一共四个指针。

代码如下:

ListNode *swapPairs(ListNode *head) {
    ListNode *dummyNode = new ListNode(0);
    dummyNode->next = head;
    if (head == nullptr || head->next == nullptr)
        return head;
    else {
        ListNode *Pre = dummyNode;
        ListNode *Left = dummyNode->next;
        ListNode *Right = dummyNode->next->next;
        while (Right != nullptr) {
            ListNode* temp = Right->next;
            Pre->next = Right;
            Right->next = Left;
            Left->next = temp;
            if (temp == nullptr)
                break;
            Pre = Left;
            Left = temp;
            Right = temp->next;
        }
    }
    head = dummyNode->next;
    delete dummyNode;
    return head;
}

tips:在while循环中创建的变量,在循环结束后会自动回收。新创建的不再使用的结点尽量使用delete回收内存空间。

2.删除倒数位置的结点

题目链接:. - 力扣(LeetCode)

讲解链接:代码随想录 (programmercarl.com)

视频讲解:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点_哔哩哔哩_bilibili

本题直观的解法是遍历两遍,第一遍得到链表长度,第二遍找到指定位置。

而使用双指针法可以做到遍历一遍找到位置,方法是构造位置相差n(删除的是倒数第n位置)位的两个指针。前指针从虚头结点开始,两个指针同步向后移动,当后指针到达末尾,前指针所指位置就是目标位置的前一位(为了方便删除结点,所以定位到前一位)。

代码如下:

ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode *dummyNode = new ListNode(0);
    dummyNode->next = head;
    ListNode *slowIndex = dummyNode;
    ListNode *fastIndex = dummyNode;
    while (n-- && fastIndex != nullptr){
        fastIndex = fastIndex->next;
    }
    while (fastIndex->next != nullptr){
        slowIndex = slowIndex->next;
        fastIndex = fastIndex->next;
    }
    ListNode* temp = slowIndex->next;
    slowIndex->next = temp->next;
    delete temp;
    head = dummyNode->next;
    delete dummyNode;
    return head;
}

tips:指针的移动使用p=p->next而不是++p,++p会让指针在内存中前进一个所指内容大小的地址,而链表并不是连续存储的,所++p不适用于指针移动。

3.两链表的公共部分

题目链接:. - 力扣(LeetCode)

讲解链接:代码随想录 (programmercarl.com)

本题要要找到两链表的公共部分,而这个公共部分存在于两个链表的后半部分。要找到这个公共部分的起点,其实只要让两个指针分别遍历两个链表,直到他们所指内容相同即可(公共部分不是说两条链表后半部分拥有同样地两份结点,而是他们共用一套结点)。

那么问题是两个链表的长度不一样,两个指针应该如何移动。如果是两层循环,那么一定是O(n^2)的复杂度。既然我们得知他们后半部分一样,那么长一点的链表多出来的部分,一定不是公共部分。所以我们可以跳过这个部分,然后两个指针同步移动即可。那么如何跳过?我们只需要得知长链表比短链表长多少即可,所以要得到两个链表的长度,就能解决该问题。

代码如下:

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    int lengthA = 1;
    int lengthB = 1;
    ListNode *curA = headA;
    ListNode *curB = headB;
    while (curA != nullptr){
        curA = curA->next;
        ++lengthA;
    }
    while (curB != nullptr){
        curB = curB->next;
        ++lengthB;
    }
    curA = headA;
    curB = headB;
    int offset;
    if (lengthB > lengthA){
        swap(curA,curB);
        swap(lengthA,lengthB);
    }
    offset = lengthA - lengthB;
    while (offset--){
        curA = curA->next;
    }
    while (curA != nullptr){
        if (curA == curB)
            return curA;
        else{
            curA = curA->next;
            curB = curB->next;
        }
    }
    return nullptr;
}

tips:如果有两段结构完全一致的代码,可以思考是否可以简化。如上面比较两个链表长度的部分,正常我们要分两个情况分别写两段代码。可以使用如上述交换的方式将两种情况的后续处理变成同一种,这样就省去了一段相似的代码。

4.循环链表

题目链接:. - 力扣(LeetCode)

讲解链接:代码随想录 (programmercarl.com)

视频讲解:把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II_哔哩哔哩_bilibili

本题难度较高,主要在数学逻辑的思考。这里贴上代码随想录的分析,说的很仔细,理解完成后该题就很好写代码了。

判断链表是否有环

可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢

首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。

那么来看一下,为什么fast指针和slow指针一定会相遇呢?

可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。

会发现最终都是这种情况, 如下图:

142环形链表1

fast和slow各自再走一步, fast和slow就相遇了

这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

动画如下:

141.环形链表

#如果有环,如何找到这个环的入口

此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。

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

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

动画如下:

142.环形链表II(求入口)

那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

代码如下:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值