【LeetCode】day4:24 - 两两交换链表中的节点, 19 - 删除链表的倒数第N个节点, 160 - 相交链表, 142 - 环形链表

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

题目描述:

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
在这里插入图片描述
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]

首先考虑到统一对头节点特殊情况的判断,定义指向head的dummy节点。要实现当前节点A和下一个节点B的两两交换需完成:记录A节点的前节点pre,记录B节点的后节点tmp=B->next,交换节点指向即B指向A,然后pre指向B以及A指向tmp,如下图
在这里插入图片描述
由于在交换时需要获取到被交换节点的next,所以需额外确保B不为nullptr。当B为nullptr时,由于在循环的上一步中已执行指向当前A的操作,不需要再执行其他操作。代码如下:

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* pre = dummy;
        ListNode* cur = dummy->next;
        while (cur && cur->next) {
            ListNode* front = cur;
            ListNode* back = cur->next;
            cur = back->next;
            pre->next = back;
            back->next = front;
            front->next = cur;
            pre = front;
        }
        return dummy->next;
    }
};

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

题目描述:

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
在这里插入图片描述
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]

直观的思路是首先获取链表的节点个数len,删除链表的倒数第N个节点即删除从head处开始的第len-n个节点,定义两个指针pre和cur分别从dummy和head移动,cur在遍历步长结束时指向需要被删除的节点,而这里的pre用来记录cur的前节点用于删除。代码如下:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* pre = dummy;
        ListNode* cur = head;
        int len = 0;
        while (cur) {
            cur = cur->next;
            ++len;
        }
        int stride = len - n;
        cur = head;
        while (stride-- > 0) {
            pre = pre->next;
            cur = cur->next;
        }
        pre->next = cur->next;
        delete cur;
        return dummy->next;
    }
};

除此之外,也可以使用快慢指针的方式,令fast指针先从dummy出发前进n步,之后slow和fast同时移动直到fast移动到链表的末尾(fast && fast->next)。因为fast提前出发前进了n步,也就是说slow和fast之间的距离是n,所以当fast再次出发前进到尾部时slow指向的便是倒数第n+1个元素(因为fast所在位置是倒数第1个元素)。代码如下:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* slow = dummy;
        ListNode* fast = dummy;
        while (fast && n--) {
            fast = fast->next;
        }
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next;
        }
        ListNode* tmp = slow->next;
        slow->next = tmp->next;
        delete tmp;
        return dummy->next;
    }
};

160.相交链表

题目描述:

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
在这里插入图片描述
题目数据 保证 整个链式结构中不存在环。
示例:略

思路为分别获取到A和B的长度,元素更多的链表在遍历时提前出发,提前出发的步长为两链表长度的差值,这样可以确保可以同时到达公共交点处(若存在)。代码如下:

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0;
        int lenB = 0;
        while (curA) {
            curA = curA->next;
            ++lenA;
        }
        while (curB) {
            curB = curB->next;
            ++lenB;
        }
        curA = headA;
        curB = headB;
        int dif;
        if (lenA > lenB) {
            dif = lenA - lenB;
            while (dif--) {
                curA = curA->next;
            }
        } else {
            dif = lenB - lenA;
            while (dif--) {
                curB = curB->next;
            }
        }
        while (curA && curB) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return nullptr;
    }
};

同样基于这种思路进行优化,目的是确保同时到达公共交点位置,A和B在同时出发到达尾部时继续另一个链表的头部出发。这样两者在同时完成第二次遍历时走过的长度均为lenA+lenB,且若公共交点存在则一定会同时到达。代码如下:

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

142.环形链表

题目描述:

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

若链表中有环则对链表进行遍历时循环永远不会退出(cur != nullptr),同时若定义快慢两指针分别以固定步长1和2在链表中移动,则一定会相交。因为在每次移动时fast在移动后总会比slow多前进一格(索引:[0, 0], [1, 2], [2, 4], [3, 6], …),也就是每步一格远离;同理在环中fast追赶slow时也是会以每步一格接近,(以1为最小单位)所以一定会相遇。
在这个前提下可以得到两指针相交的节点。按照图中标示进行命名:
在这里插入图片描述
slow到达相交节点时长度设为n,则
{ ( x + y ) = n (1) ( x + y ) + k ( y + z ) = 2 n ( 2 ) \begin{cases} (x + y) = n \quad \text{(1)} \\ (x + y) +k(y+z)=2n \quad(2) \end{cases} {(x+y)=n1(x+y)+k(y+z)=2n2
可以拆分为 x + y x+y x+y两者共同走过的距离 n n n,以及 ( z + k ( y + z ) ) (z+k(y+z)) (z+k(y+z))表示fast以比slow每步多走一格在环中绕圈再次停到相交点的距离 n n n
令slow指针从起点出发,到达环入口时所走的距离如下(公式1) d i s t a n c e S l o w = x = n − y distanceSlow=x=n-y distanceSlow=x=ny令fast从相遇点出发,步长和slow相同为1前进,到达环入口时所走距离为(公式2) d i s t a n c e F a s t = z + h ( y + z ) = h ′ ( y + z ) − y = n − y distanceFast=z+h(y+z)=h'(y+z)-y=n-y distanceFast=z+h(y+z)=h(y+z)y=ny无论fast在环中再次转了几圈,两者下次的相遇点一定为环入口。代码如下:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) {
                slow = head;
                while (fast != slow) {
                    slow = slow->next;
                    fast = fast->next;
                }
                return fast;
            }
        }
        return nullptr;
    }
};
  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值