代码随想录DAY04 - 链表 - 8/03

两两交换链表中的节点

题干

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

链接:. - 力扣(LeetCode)

思路

两两交换节点,让我联想到昨天做的反转链表的题目,两两交换节点相当于反转一次指针,只不过不是连续反转,而是每隔两个节点再反转。或许这道题也可以使用双指针法,同时为了保证操作统一,继续使用虚拟头节点dummyHead。画草图的时候发现,原来反转链表只需改变两个指针,而交换节点需要改变三个指针。

代码

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr){
            return head;
        }
        // 新建虚拟头节点
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        // 新建三个指针
        ListNode* prepre = dummyHead;
        ListNode* pre = head;
        ListNode* cur = pre->next;
        while (pre != nullptr && cur != nullptr){ // pre 和 cur 分别指向两个需要交换的节点,都不能为空
            ListNode* tmp = cur->next; // cur->next有可能为 null
            // 两两交换
            cur->next = pre;
            prepre->next = cur;
            pre->next = tmp;
            // 移动到下一对节点
            prepre = pre;
            pre = prepre->next; // 此时 pre 有可能为空指针,所以接下来要判空
            if (pre != nullptr){
                cur = pre->next;
            }
        }
        return dummyHead->next;
    }
};

删除链表倒数第 N 个结点

题干

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

链接:. - 力扣(LeetCode)

思路

要删除倒数第 n 个结点,关键在于如何找到倒数第 n 个结点,即尾结点的前 (n-1) 个结点。我们可以设置两个指针 del 和 cur,指针 cur 指向当前结点,用于遍历链表找到尾节点;指针 del 则指向 cur 的前 (n-1)个结点,用于找到待删除的结点。只要 cur 指向了尾结点,则 del 就指向了倒数第 n 个结点。同时为了能够方便删除结点 del,可以再设置一个指针 pre 指向 del 的前一个结点,删除时只需要 pre->next = del->next。

代码

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        // 新建虚拟头节点
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        for (int i = 0; i < n; ++i) {
            cur = cur->next;
        }
        ListNode* del = head;
        ListNode* pre = dummyHead;
        // 查询倒数第 n 个结点
        while (cur->next != nullptr){
            cur = cur->next;
            pre = del;
            del = del->next;
        }
        // 删除倒数第 n 个结点
        pre->next = del->next;
        delete del;
        return dummyHead->next;
    }
};

链表相交

题干

题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。注意结点的指针相同才意味着相交,如果只是数值相同而指针不同不意味着相交。

链接:. - 力扣(LeetCode)

思路

方法一:暴力解法,用两个不同的指针 curA 和 curB 遍历两个链表,对比 curA 和 curB 指针是否相同,时间复杂度O(n^2)。之后发现自己想复杂了,本质上是没有完全理解题意中的结点相交到底代表着什么意思。

当两个链表存在相交结点,都说明了什么?

(1)两个链表自相交结点后的所有结点都相同。

因为链表结点的结构只定义了一个 next 指针,一旦两个链表中间存在相交结点,则从相交结点开始一直跟踪 next 指针往后的所有结点都相同。我看到 ”相交“ 两个字就想到了数学里两直线交叉的情况,先入为主觉得两个链表相交会存在以下这种情况,犯傻了。。

(2)若长链表的长度为 sizeA,短链表长度为 sizeB,长链表中相交结点最靠前的位置只会出现于第 (sizeA - sizeB) 个节点处,所以长链表的遍历并不需要从头节点开始。

由第(1)个结论可以知道,两个链表如果相交,则他们的末尾结点应该都相同,所以最极端的情况是整个短链表刚好就是长链表的末尾部分,此时会出现相交结点最靠前的位置 sizeA - sizeB。另一方面,也说明了两个链表的结点指针进行比较的时候,为什么当各自结点的指针都不同时就可以同步前进,即当 curA != curB 时,curA = curA->next,curB = curB->next,而不是 curA 保持不变,curB = curB->next。

方法二修正后:先计算两个链表的长度 sizeA,sizeB;设长链表为headA,如果不是则交换。再分别用两个指针 curA 和 curB 遍历链表,其中 curA 初始为长链表中第(sizeA - sizeB)的位置,curB 初始则为headB,之后两两比较,curA 和 curB 不同则一起前进。时间复杂度 O(n)。

代码

方法一:暴力解法(虽然能通过,但逻辑有问题)
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        // 结点数目有可能为 0
        if (headA == nullptr || headB == nullptr){
            return nullptr;
        }
        // 只是查找,不需要新建虚拟头节点
        ListNode* curA = headA; // 遍历链表 A
        ListNode* curB = headB; // 遍历链表 B
        while (curA != nullptr){
            while (curB != nullptr){
                if (curA == curB){
                    return curA;
                } else{
                    curB = curB->next;
                }
            }
            curA = curA->next;
            curB = headB;
        }
        return nullptr;
    }
};
方法二:改正后的解法
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int sizeA=0,sizeB=0; // 记录链表A和链表B的长度
        ListNode* curA = headA; // 用于遍历链表 A
        ListNode* curB = headB; // 用于遍历链表 B
        while (curA != nullptr){
            sizeA++;
            curA = curA->next;
        }
        while (curB != nullptr){
            sizeB++;
            curB = curB->next;
        }
        // 将长链表固定设置为 链表 A
        if (sizeA < sizeB){
            ListNode* tmp = headB;
            headB = headA;
            headA = tmp;
            // delete tmp; 
            // 此处不可以释放掉 tmp 结点,如果释放掉 tmp,则相当于 headA 所指结点被释放
            // 如果delete,后续访问指针 headA 会报错
            int tmpSize = sizeB;
            sizeB = sizeA;
            sizeA = tmpSize;
        }

        curA = headA;
        curB = headB;
        int n = sizeA - sizeB;
        for (int i = 0; i < n; ++i) {
            curA = curA->next; 
            // 如果前面 delete tmp,则此处会报错 ERROR: AddressSanitizer: heap-use-after-free on address
            // 此报错表明程序试图访问已经被释放的堆内存
        }
        while (curA != nullptr && curB!= nullptr){
            if (curA == curB){
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return nullptr;
    }
};

环形链表

题干

题目:给定一个链表的头节点 head,返回链表开始入环的第一个节点。 如果链表无环,则返回 null 。不允许修改 链表。

如何判定存在环?

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。

为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 - 1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

链接:. - 力扣(LeetCode)

思路

先确定链表有环,再找到环的入口结点。

1)如何判断是否有环?

不知道有什么方法,直接看的题解。

快慢指针法:一个快指针 fast,一个慢指针 slow,fast 每次移动两个结点,slow 每次移动一个结点。如果链表中没有环,则快慢指针不可能相遇;如果链表有环,则 fast 和 slow 会相遇。为什么链表有环,快慢指针就一定会相遇呢?不会错过吗?

首先 fast 肯定会比 slow 先进入环,而 fast 的速度是每次移动两个结点,slow 的速度是每次移动一个结点,fast 相对于 slow 每次前进( 2 - 1 = 1 )个结点,则进入环以后,fast 肯定会以每次移动一个结点的速度追上 slow。

2)如何找到环的入口结点?

观察得到,环的入口节点将有两个指针指向它,其余结点都只有一个指针指向自身。(个人想法,本来想用这个方法做题的,但是好像做不出来)

看了题解,根据快慢指针法,fast 和 slow 相遇的结点位置和环的入口位置有什么关系呢?

时间不够,剩下的明天再学。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值