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

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

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

题目链接:24.两两交换链表中的节点

虚拟头节点法

虚拟头节点法
思路:设置一个虚拟头节点,统一头节点和其他节点的操作。 在做题时建议画图,因为涉及到多个指针,容易混乱。对于本题,最后返回的头节点是 dummyNode.next。

/**
 * 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) {
        ListNode dummyNode = new ListNode(0); //设置虚拟头节点
        dummyNode.next = head; //链接虚拟头节点和原头节点,方便后续操作
        ListNode cur = dummyNode;
        //while循环内条件分别代表偶数个节点和奇数个节点的情况,
        //且顺序不能调换,否则会导致空指针异常
        while (cur.next != null && cur.next.next != null){
            ListNode temp1 = cur.next; //记录临时节点1
            ListNode temp2 = cur.next.next.next; //记录临时节点2
            
            cur.next = cur.next.next; //步骤一
            cur.next.next = temp1; //步骤二
            cur.next.next.next = temp2; //步骤三
            
            cur = cur.next.next; //cur移动两位,准备下一轮交换
        }
        return dummyNode.next;
    }
}

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

题目链接:19.删除链表的倒数第N个节点

虚拟头节点 + 快慢指针法

思路:本题关键点在于如何找到倒数第 n 个节点。 虚拟头节点的作用在于统一操作,快慢指针的作用在于方便寻找倒数第 n 个节点的前一个节点。

/**
 * 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 removeNthFromEnd(ListNode head, int n) {
        ListNode dummyNode = new ListNode(-1); //虚拟头节点
        dummyNode.next = head;
        //设置快、慢指针
        ListNode fast = dummyNode;
        ListNode slow = dummyNode;
        
		//让快指针先走n + 1步,
		//旨在当fast指向尾节点的后一位(null)时,slow恰好指向待删除节点的前一个节点
        while (n-- != 0 && fast != null){
            fast = fast.next;
        }
        fast = fast.next; //快指针多走1步
        
		//快、慢指针同时移动
        while (fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next; //删除节点
        return dummyNode.next;
    }
}

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

题目链接:160. 相交链表

虚拟头节点 + 快慢指针法

思路:本题关键点在于:相交节点不是指节点的值相等,而是指节点在内存中的位置相等。 主要过程为求出两链表长度的差值,然后将长链表与短链表尾部对齐,之后便可以比较 cur1 和 cur2。如果两指针相同则返回任一节点,否则同时向后移动 cur1 和 cur2。循环至两指针指向 null 时,则说明两个链表不存在相交节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        int len1 = 0, len2 = 0; //用于统计两链表的长度
        while (cur1 != null) { //求链表A的长度
            len1++;
            cur1 = cur1.next;
        }
        while (cur2 != null) { //求链表B的长度
            len2++;
            cur2 = cur2.next;
        }
        //重置cur1和cur2
        cur1 = headA;
        cur2 = headB;
        //保证cur1指向较长的链表
        if (len2 > len1) {
            int tempLen = len1;
            len1 = len2;
            len2 = tempLen;
            ListNode tempNode = cur1;
            cur1 = cur2;
            cur2 = tempNode;
        }
        int gap = len1 - len2; //求长度差
        //让cur1和cur2在同一起点上(末尾位置对齐)
        while (gap-- > 0) { //此处 != 和 > 均可
            cur1 = cur1.next;
        }
        //比较curA和curB是否相同,如果相同则返回任一节点;
        //否则同时向后移动cur1和cur2
        while (cur1 != null) {
            if (cur1 == cur2) {
                return cur1;
            }
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        //如果两个链表不存在相交节点,返回 null 。
        return null;
    }
}

142. 环形链表 II

题目链接:142. 环形链表 II

快慢指针法

思路:本题主要考察对链表的操作和一些数学运算。 要点如下:

  • 判断链表是否有环;
  • 如果有环,如何找到环的入口。

①判断链表是否有环
使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast 指针每次移动两个节点,slow 指针每次移动一个节点,如果 fast 和 slow 指针在途中相遇 ,说明这个链表有环。原因在于相对于 slow 指针来说,fast 指针是一个节点一个节点靠近 slow 指针的,因此二者一定会相遇。

②寻找环的入口
假设从头结点到环形入口节点的节点数为 x,环形入口节点到 fast 指针与 slow 指针相遇节点 节点数为 y,从相遇节点再到环形入口节点节点数为 z。 如图所示:
寻找环的入口
(1)快慢指针相遇时,slow 指针走过的节点数为: x + y, fast 指针走过的节点数:x + y + n (y + z),n代表 fast 指针在环内走了 n 圈才遇到 slow 指针,(y + z)为一圈内节点的个数。

(2)因为 fast 指针是一步走两个节点,slow 指针一步走一个节点, 所以 fast 指针走过的节点数 = slow 指针走过的节点数 * 2,即 (x + y) * 2 = x + y + n (y + z),两边消掉一个(x + y),即 x + y = n (y + z)。因为 x 表示头结点到环形入口节点的的距离,因此移项将 x单独放在等号左边:x = n (y + z) - y,再从 n(y + z) 中提出一个(y + z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z。此处 n 一定是 >=1 的,因为 fast 指针至少要多走一圈才能遇到 slow 指针。

这个公式说明:

  1. 对于 n = 1 的情况,意味着 fast 指针在环形里转了一圈之后,就遇到了 slow 指针。当 n 为1的时候,公式就化解为 x = z,这就意味着,从头结点处相遇节点处各出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。也就是在相遇节点处,定义一个指针 index1,在头结点处定一个指针 index2。让 index1 和 index2 同时移动,每次移动一个节点, 那么他们相遇的地方就是环形入口的节点。
  2. 对于 n > 1 的情况,意味着 fast 指针在环中转 n 圈之后才遇到 slow 指针。而这种情况和n = 1的时的效果是一样的,只不过 index1 指针在环里多转了 (n-1) 圈,然后再遇到 index2 指针,相遇点依然是环形的入口节点。

③为什么 slow 指针在环中的第一圈就会遇到 fast 指针?
当 slow 指针进环的时候,fast 指针一定是在环的任意一个位置,如图所示:
slow与fast相遇
则 fast 指针走到环入口3的时候,已经走了 k + n 个节点,slow 指针相应的应该走了 (k + n) / 2 个节点。因为 k 是小于 n 的(图中可以看出),所以 (k + n) / 2 一定小于 n。也就是说 slow 指针一定没有走到环入口3,而 fast 指针已经到环入口3了,即在 slow 指针开始走的那一环已经和 fast 指针相遇了。而 fast 指针相对于 slow 指针是一次移动一个节点,所以不可能跳过去。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                ListNode index1 = fast;
                ListNode index2 = head;
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值