代码随想录算法训练营第四天 | 24. 两两交换链表中的节点 19. 删除链表的倒数第 N 个结点 02.07. 链表相交 142. 环形链表 II Java实现

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

题目链接:https://leetcode.cn/problems/swap-nodes-in-pairs/description/

解题思路

类似双指针法,两两交换,就是每两个一组,进行内部交换,交换就可以使用双指针法。
注意点:需要有个指针来保存上一组的尾结点,用于执行下一组被交换后的头结点。

代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode cur = head.next;
        ListNode prev = head;
        ListNode lastGroup = null; // 上一组的尾节点
        head = head.next; // 反转后,head的下一个节点成为了头节点
        while(true) {
            // 保存下一组节点的第一个节点
            ListNode next = cur.next;
            // 反转当前组的节点
            cur.next = prev;
            prev.next = next;
            // 将上一组的尾节点指向当前新的头节点
            if (lastGroup != null) {
                lastGroup.next = cur;
            }
            // 跳转到下一组节点
            if (next == null || next.next == null) {
                break;
            }
            lastGroup = prev;
            cur = next.next;
            prev = next;
        }
        return head;
    }
}

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

题目链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/

解题思路

数组存储法,内存占用较多

使用一个数组来存储整个链表的,遍历整个链表后,就可以使用O(1)来删除指定节点。
代码如下:

class Solution {
  public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode[] indexNode = new ListNode[30];
        int index = 0;
        while(head != null) {
            indexNode[index++] = head;
            head = head.next;
        }
        // 如果删除的是头节点,则返回头结点执行的下一个节点
        if (n == index) {
            return indexNode[1];
        }
        indexNode[index-n-1].next = indexNode[index-n].next;
        return indexNode[0];
    }
}

双指针法

该题是双指针的经典案例,使用一个first节点从头往后先移动n个节点,当first到达第n个节点后,second节点和first开始同步向后移动,当first到达结尾后,second节点即是待删除的节点。
代码如下:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode mkHead = new ListNode(0, head); // 虚拟头结点
        ListNode deletePrevNode = mkHead;
        while(head != null) {
        	// 当first移动到n个节点后,待删除的上一个节点一起移动
            if (n-- <= 0) {
                deletePrevNode = deletePrevNode.next;
            }
            head = head.next;
        }
        // 删除节点
        deletePrevNode.next = deletePrevNode.next.next;
        return mkHead.next;
    }
}

02.07. 链表相交

题目链接:https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/description/

解题思路

链表相交后的节点,都是一样的,所以只需要判断出两个链表相差多少个节点,然后使用双指针法,同时向后移动,同时判断两个链表的节点是否相同
复杂代码如下:

public class Solution {
   public static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode mkHeadA = new ListNode(0, headA);  // 设置虚拟头节点
        ListNode frontA = mkHeadA;
        ListNode afterA = mkHeadA;

        ListNode mkHeadB = new ListNode(0, headB);// 设置虚拟头节点
        ListNode frontB = mkHeadB;
        ListNode afterB = mkHeadB;

        // 遍历链表A/B,frontA、frontA跳转到最后一个节点
        ListNode rtn = null;
        while(true) {
            // 如果两个链表都到尾结点了
            if (frontA.next == null && frontB.next == null) {
                // 如果两个链表的最后一个节点都不是相交的,则表示没有相交节点
                if (frontA != frontB) {
                    break;
                }
                // 如果两个链表的最后一个节点是相交的,则afterA 和 afterB一起向后移动,直到afterA == afterB
                while(afterA != afterB) {
                    afterA = afterA.next;
                    afterB = afterB.next;
                }
                rtn = afterA;
                break;
            }
            // 如果frontA到尾节点了,则afterB需要和frontB一起往后移动
            if (frontA.next == null && frontB.next != null) {
                afterB = afterB.next;
            }
            // 如果frontB到尾节点了,则afterA需要和frontA一起往后移动
            if (frontB.next == null && frontA.next != null) {
                afterA = afterA.next;
            }

            // 遍历链表A
            if (frontA.next != null) {
                frontA = frontA.next;
            }
            // 遍历链表B
            if (frontB.next != null) {
                frontB = frontB.next;
            }
        }
        return rtn;
    }
}

合并链表实现同步移动

使用两个指针同时移动,如果某个链表到尾部了,则跳转到另一个链表的头节点,同步移动。这样如果两个链表都到达尾部,并且都跳转到另一个节点后,先跳转的指针移动的节点数,就是两个链表相差的节点数。
代码如下:

public class Solution {
    public static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 = headA, p2 = headB;
        while(p1 != p2) {
            if (p1 != null) {
                p1 = p1.next;
            } else {
                p1 = headB;
            }
            if (p2 != null) {
                p2 = p2.next;
            } else {
                p2 = headA;
            }
        }
        return p1;
    }
}

142. 环形链表 II

题目链接:https://leetcode.cn/problems/linked-list-cycle-ii/description/

暴力解法

设置一个列表,存储已走过的节点地址,判断列表中是否存在相同的地址,如果存在,则是环的入口节点地址。
优点:代码简洁,思路清晰。
缺点:空间复杂度为O(n)
时间复杂度:O(n)
空间复杂度:O(n)
代码如下:

public class Solution {
    public ListNode detectCycle0(ListNode head) {
        Set<ListNode> visited = new HashSet<>();
        ListNode cur = head;
        while(cur != null) {
            if (!visited.add(cur)) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }
}

快慢指针

分别定义一个 fast 指针和一个 slow 指针,fast指针每次移动两个节点,slow每次移动一个节点。如果fast指针和slow指针重合了,就表示该链表有环。
动画参考:
在这里插入图片描述
这里确认链表有环后,就是找出链表环的入口节点,这里需要用到数学知识:
假设头结点到环形入口节点的节点数为x,slow指针和fast指针相遇时,slow指针在环内移动的节点数为y,环链表剩下的节点数为z。
如图所示:
在这里插入图片描述
因为fast指针的移动速度是slow指针的两倍,且fast节点可能在环内已经移动了n圈,所以有以下公式:
(x + y) * 2 = x + y + n*(y + z)

我们如果要找出环的入口节点,即需要算出x,公式化简:
x + y = n * (y + z)
x = n * (y + z) - y
也就是说,如果设置index1、index2两个指针,分别从头结点和入口节点同时开始移动,且每次移动一个节点。那么,当index1到达入口节点时,index2已经跑了n圈,且还差 y 个节点就到达入口节点了。
再假设 index1 从头结点开始移动,index2从入口节点后面的 y 个节点开始移动,那当index1到达入口节点时,index2是不是也刚好到达入口节点。
而入口节点后面的 y 个节点,不就是 slow 指针和 fast 指针相遇时的节点吗?

总结:
分别定义一个 fast 指针和一个 slow 指针,fast指针每次移动两个节点,slow每次移动一个节点,
如果fast 和 slow没有相遇,表示没有环。
如果fast 和 slow指针相遇,表示有环。此时再设置index1指针从头结点开始,index2指针从相遇节点开始,同时以一个节点的速度开始移动,当index1和index2相遇时,就是入口节点。

时间复杂度:O(n)
空间复杂度:O(1)
代码如下:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            // 快慢指针相遇,表示有环
            if (fast == slow) {
                ListNode ringStart = head;
                // 右环后,再用一个指针从头结点出发,slow指针继续走,如果相遇,则该节点就是环的开始节点
                while (ringStart != slow) {
                    ringStart = ringStart.next;
                    slow = slow.next;
                }
                return ringStart;
            }
        }
        return null;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值