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

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

题目链接:24. 两两交换链表中的节点 - 力扣(Leetcode)

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

解题思路:

    本题的关键点在于两两交换,所以把链表的节点两两分组,分别交换完之后,指针由上一组的末尾节点指向下一组的第一个节点即可。

图解如下:

    首先定义一个虚拟头指针,分别记录交换后第二、第三个节点的位置,也就是1和3;为什么2的位置不需要用temp存一下呢?因为2是交换后的第一个节点,操作的时候直接用cur指向cur->next->next就可以了。第一组交换完之后,将cur移动至第一组的最后一个节点,此时cur也就作为了下一组的虚拟头节点,循环操作即可,直到cur的next或者nextnext为空,这表示此时剩下的节点已经不能构成两个一组了,则不需要交换了。

/**
 * 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* swapPairs(ListNode* head) {
        ListNode *dummyHead = new ListNode(0);                 //创建一个虚拟头节点
        dummyHead->next = head;
        ListNode *cur = dummyHead;                             //创建临时指针遍历链表
        while(cur->next !=nullptr&&cur->next->next!=nullptr){  //画图可知,包括虚拟头节点,三个三个一组,所以cur的next和nextnext都不能为空
            ListNode *temp = cur->next;                        //记录原链表第一个节点,两两分组的第一组开头
            ListNode *temp1 = cur->next->next->next;           //记录原链表第三个节点。两两分组的第二组开头,用来让上一组最后一个节点指向这里;

            cur->next=cur->next->next;                         //步骤一:cur指向原链表第二个节点
            cur->next->next=temp;                              //步骤二:原链表第二个节点指向第一个节点,实现第一组的交换位置
            cur->next->next->next=temp1;                       //第一组结束,最后一个节点指向下一组的第一个节点

            cur= temp;                                         //让cur到达第一组最后一个节点位置,成为下一组的新的头节点,然后继续循环
        }
        return dummyHead->next;                                //最后返回新的头节点
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

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

题目链接:19. 删除链表的倒数第 N 个结点 - 力扣(Leetcode)

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

解题思路:

    本体核心关键就在于如何在找到链表中倒数第n+1个数的指针位置,只要找到这个位置,让它指向nextnext就可以直接删除倒数第n个数了。之前我们通过while(n--)的方法可以找到正向第n个数,但是链表size未知,所以这种方法似乎不可行了。于是考虑双指针法,让一个指针先走n+1步,然后两个指针一起移动,直到先走的指针指到null了则表示以及遍历完链表了,此时后走的指针就是倒数第n+1个数的位置了。

/**
 * 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* fast = dummyHead;                            //定义一个快指针
        ListNode* slow =dummyHead;                             //定义一个慢指针
        while(n--&&fast!=nullptr){                             //快指针先向前走n步
            fast = fast->next;
        }
        fast = fast->next;                                     //多走一步,为了让fast和slow之间差n步,这样同时走完时,slow会指向倒数第n个的前一个
        while(fast!=nullptr){                                  //fast一直遍历,直到指向最后的null,fast和slow一起向后移动
            fast = fast->next;
            slow = slow->next;
        }                                                      //此时结果,fast指向最后的null,slow指向倒数第n+1个节点
        slow->next = slow->next->next;                         //利用slow删除倒数第n个节点
        return dummyHead->next;                                //返回此时的头节点
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

面试题 02.07. 链表相交(160.链表相交)

题目链接:面试题 02.07. 链表相交 - 力扣(Leetcode)

解体思路:

    根据题意,只要两个链表有相交,则从相交的位置开始后面的节点值都相同。那么如何找到第一个相同的节点呢,我们就可以想到先把链表尾对齐,然后让两个链表指针从较短的一条链表头节点位置开始同时移动,当两个相等的时候那就是交点。

方法一:暴力尾对齐

这就是如下的暴力对齐法,将链表尾对齐,通过计算两个链表长度,求出长度差gap,让长链表指针先移动gap,此时两个链表位置相同,同时步进求出相同节点,最后返回相交位置的指针。

/**
 * 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* curA = headA;
        ListNode* curB = headB;
        int lenA = 0; int lenB = 0;
        while(curA !=NULL){                  //计算A链表长度
            lenA++;
            curA = curA->next;
        }
        while(curB !=NULL){                  //计算B链表长度
            lenB++;
            curB = curB->next;
        }
        curA = headA;                        //把两个遍历的指针重置回头节点
        curB = headB;
        if(lenB>lenA){                       //确保A链表长与B链表,逻辑上无所谓,主要是便于下面的代码编写,不需要考虑两种情况
            swap(lenA,lenB);                 //swap交换位置
            swap(curA,curB);
        }
        int gap = lenA - lenB;               //计算两个链表长度差
        while(gap--){
            curA = curA->next;
        }                                    //让更长的链表上的cur节点先走gap步,把两个链表尾对齐,此时curA和curB已经对齐。
        while(curA!=NULL){                   //不为空的时候,两个cur一起往后遍历
            if(curA == curB)return curA;     //当两个cur的值相等则返回curA节点,或者curB也行,相当于是把两个融合了,舍去了另一边的后续
            curA = curA->next;               
            curB = curB->next;
        }
        return curA;                       //最后返回curA,即为相交的节点,即使两个都遍历到null也不相交,此时cur指向了null,返回的也是null符合题意
    }
};
  • 时间复杂度:O(n + m)
  • 空间复杂度:O(1)

方法二:对换头节点法

该方法核心思想是,当一个链表头节点遍历完到null位置后,就直接指向另一个链表的头节点,当第二个指针到达另一个链表上的时候,两个节点就必然处于方法一那张图的状态。

/**
 * 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) {
        if (headA == NULL || headB == NULL) {                    //当任意一个链表是空链表,则无法相交
            return NULL;
        }
        ListNode *pA = headA, *pB = headB;
        while (pA != pB) {                                       //当两个节点不相同的时候
            pA = pA == NULL ? headB : pA->next;                  //当pa不为空就遍历下一位,直到遍历完A链表,指向B的头节点
            pB = pB == NULL ? headA : pB->next;                  //同上,B遍历完就指向A头节点
        }                                                   //上述两行,pa和pb都指向了另一个链表之后,将尾巴对齐,pa和pb此时必然是并驾齐驱的同位置
        return pA;                           //pa和pb同时移动,直到pa=pb*(此处其实是在判断数值相等),则返回此时的pa或pb节点,相当于把后续融合了
    }
};
  • 时间复杂度:O(n + m)
  • 空间复杂度:O(1)

142.环形链表II

题目链接:142. Linked List Cycle II - 力扣(Leetcode)

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

解题思路:

   本体代码逻辑并不复杂,难点在于代码前的数学推导,如何通过简单的指针移动来找到环的入口位置呢?本题的思想是通过定义两个快慢指针fast和slow,fast一次走两步,slow一次走一步,让fast先进入环内然后追击slow指针。

 根据上图可以推导:

当fast和slow相遇时: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-1)y+nz ,

再从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){           //fast一次要移动两位,所以fast不能为空并且fast->next也不能为空
            fast = fast->next->next;                   //fast一次移动两位
            slow = slow->next;                         //slow一次移动一位
            if(fast==slow){                            //当fast和slow第一次相遇时,必然是在环内的
                ListNode* index1 = fast;               //将相遇位置定义一个指针index1
                ListNode* index2 = head;       //头节点定义一个临时指针index2,根据公式推导,可知当index1和index2同步移动,相遇位置就是环形入口
                while(index1!=index2){                 //同步移动直到两个index相同
                    index1= index1->next;
                    index2= index2->next;
                }
                return index1;                         //返回相遇位置index1或者index2,即为环形入口
            }
        }
        return NULL;                                   //如果链表没有环,则fast指针一定会指向null,则跳出了外层while循环,此时直接返回null即可
    }
};
  • 时间复杂度: O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
  • 空间复杂度: O(1)

 总结:

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值