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

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

用虚拟头结点,这样会方便很多。 

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

示例 1:

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

题目链接/文章讲解/视频讲解: 代码随想录

思路:

自己写出来的第一道题, 纪念一下:

我们知道, 要删除和改变一个节点的位置, 需要知道这个节点的前继结点和后继结点. 本题两两交换我们至少需要两个结点. 我们先画图分析

第一次思考: 用一个pre指针表示cur指针的前继结点, cur->next来代表cur指针的后继结点

 

 交换结点的大致思路, 可是当cur指针指向的结点完成交换后, 其前继结点消失了, 因此我们得再加一个指针来指向当前pre指针的前继结点. 

第二次思考:用pre指针表示待交换两个结点的前继结点, cur表示待交换结点的第一个结点, tmp表示待交换结点的第二个结点

 

当前交换思路没问题, 简单写一下交换代码:

tmp = cur->next;
pre->next = tmp;
cur->next = tmp->next;
tmp->next = cur;
pre = cur;
cur = cur->next;

接着我们要交换的是结点3和结点4, 同理, 那么pre就该指着当前tmp的位置, cur指向结点3的位置, tmp指向结点4的位置..

第三次思考: 我们该在什么位置结束交换循环?

 

 结束交换后, 我发现cur->next是指向空的, 并且我们最后执行了cur = cur->next. 我们的循环条件设置的是while(cur->next != NULL), 这就导致了运行错误, 我们无法访问cur->next的地址! 那么我们应该如何结束呢?

既然如此, 我们添加一个判断条件: 如果当cur->next = NULL的时候, 我们就不改变cur的值, 否则让cur指向下一个结点, 因此我们的循环就能够成功运行:

while(cur->next != NULL){
   tmp = cur->next;
   pre->next = tmp;
   cur->next = tmp->next;
   tmp->next = cur;
   pre = cur;
   cur = cur->next ? cur->next : cur;
}

 非常完美!

最后是整个代码:

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr) return NULL;
        ListNode* dummyhead = new ListNode();
        dummyhead->next = head;
        ListNode* pre = dummyhead;
        ListNode* cur = pre->next;
        ListNode* tmp;


        while(cur->next != NULL){
            tmp = cur->next;
            pre->next = tmp;
            cur->next = tmp->next;
            tmp->next = cur;
            pre = cur;
            cur = cur->next ? cur->next : cur;
        }

        dummyhead = dummyhead->next;
        return dummyhead;
    }
};

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

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

示例 1:

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

双指针的操作,要注意,删除第N个节点,那么我们当前遍历的指针一定要指向 第N个节点的前一个节点,建议先看视频。

题目链接/文章讲解/视频讲解:代码随想录

思路:

点删除一个结点必须知道它的前继结点和后继结. 昨天的链表删除元素刚好练习了正向删除元素. 因此我的思路就是: 正向遍历整个链表然后得到链表的长度length, 然后再使用长度length - n找到索引, 通过循环递减索引, 找到要删除结点得到前置结点, 然后删除这个结点.

我的思路写下代码:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(n < 0) return head;
        ListNode* dummyHead = new ListNode(0, head);
        ListNode* pre = dummyHead;
        ListNode* cur = dummyHead;
        int length = 0;
        while(cur->next != NULL){
            cur = cur->next;
            length++;
        }
        int index = length - n;
        while(index--){
            pre = pre->next;
        }
        cur = pre->next;
        pre->next = cur->next;

        return dummyHead->next;
    }
};

看了Carl哥的思路:

使用双指针法, 一个快指针先向前移动 n+1 步, 然后使用一个慢指针在快指针遍历到链表结尾的时候随着快指针遍历. 当快指针遍历到结尾, 慢指针即遍历到需要删除的倒数第n个结点.

代码实现部分:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(n < 0) return head;
        ListNode* dummyHead = new ListNode(0, head);
        ListNode* pre = dummyHead;
        ListNode* cur = dummyHead;
        // cur向前遍历n+1次, 因为cur是从虚拟头结点开始
        while(n-- && cur != NULL){
            cur = cur->next;
        }
        cur = cur->next;

        while(cur != NULL){
            cur = cur->next;
            pre = pre->next;
        }

        // 删除结点
        pre->next = pre->next->next;

        return dummyHead->next;
    }
};

 面试题 02.07. 链表相交  

本题没有视频讲解,大家注意 数值相同,不代表指针相同。

题目链接/文章讲解:代码随想录

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

图示两个链表在节点 c1 开始相交

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

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

 思路:

1. 找到较短链表: 通过遍历链表A和链表B来记录链表的大小, 然后进行比较

2. 计算链表A和B长度差 

3. 把长链表遍历到短链表的长度大小

4. 定义一个pre指针指向公共长度部分

5. 返回pre

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA == NULL || headB == NULL) return NULL;
        // 计算链表长度
        int szA = sz(headA); // 遍历链表A计算链表A的长度
        int szB = sz(headB); // 遍历链表B计算链表B的长度

        // 长链表减到与短链表一样长
        ListNode* curA = headA;
        ListNode* curB = headB;
        int n = (szA - szB) > 0 ? (szA - szB) : (szB - szA); // 计算链表差
        if(szA >= szB){
            while(n--) curA = curA->next;
        }
        else{
            while(n--) curB = curB->next;
        }

        // 定义一个pre指针指向开始处
        ListNode* preA = curA;
        ListNode* preB = curB;

        while(curA != NULL){
            while(curA != NULL && curA->val == curB->val){
                curA = curA->next;
                curB = curB->next;
            }
            if(curA != NULL){
                preA = curA;
                preB = curB;
                curA = curA->next;
                curB = curB->next;
            }
        }

        // 返回链表相交处pre
        while(preA != preB){
            preA = preA == NULL ? NULL : preA->next;
            preB = preB == NULL ? NULL : preB->next;  
        }
        
        return preA;
    }

    // 计算链表长度
    int sz(ListNode *head){
        int sz = 1;
        ListNode* cur = head;
        while(cur != NULL){
            cur = cur->next;
            sz++;
        }

        return sz;
    }
};

注意:

这道题还有一个问题是可能有值相同, 但地址不相同的结点. 可以通过让上述的preA指针和preB指针比较地址是否相等, 相等则返回其中一个指针, 否则两个都向下继续遍历, 直到相等为止.

 142.环形链表II  

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

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

不允许修改 链表。

算是链表比较有难度的题目,需要多花点时间理解 确定环和找环入口,建议先看视频。

题目链接/文章讲解/视频讲解:代码随想录

这道题主要是思考部分难, 思路思考出来后实际代码部分较简单.

思路:

直接上代码:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == NULL || head->next == NULL) return NULL;

        ListNode* fast = head;
        ListNode* slow = head;
        ListNode* pre = head;
        ListNode* cur = head;

        // 寻找快慢指针相遇点
        while(fast != NULL && fast->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow){
                cur = fast;
                while(pre != cur){
                    cur = cur->next;
                    pre = pre->next;
                }
                return cur;
            }
        }
        
        return NULL;
    }
};

这两天的练习感觉自己已经学有小成了, 今天又复习了一遍链表的基本操作, 同时四道题中有三道题都是没有看Carl的思路自己就直接写出来了, 可喜可贺. 继续加油!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值