掘金 后端 ( ) • 2024-03-06 10:28

两两交换链表中的节点

https://leetcode.cn/problems/swap-nodes-in-pairs/description/

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

没啥可说的,画个图应该就搞出来了:

⨳ 找到两个交换节点(node_1,node_2)的前一个节点(prev_node);

prev_node 的后继指针指向 node_2(相当于把 node_1 从链表中删除节点)

node_1 的后继指针指向 node_2 的后继节点,node_2 的后继指针指向 node_1(相当于从链表中插入 node_1

为了统一对头节点的操作,设置虚拟节点是很好的选择:

/**
 * 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 dumy_head = new ListNode(0,head);// 为链表设置虚拟头节点
        ListNode pre_node = dumy_head;
        while(pre_node.next !=null && pre_node.next.next !=null  ){
            ListNode node1 = pre_node.next;    
            ListNode node2 = pre_node.next.next;
            // 将 node1 从链表中删除
            pre_node.next = node2;
            // 将 node1 重新插入链表中
            node1.next = node2.next;
            node2.next = node1;
            // 计算下一个 pre_node
            pre_node = pre_node.next.next;
        }
        // 用完dumy_head,别忘了将其从链表中拿出去
        ListNode new_head = dumy_head.next;
        dumy_head.next = null; 
        return new_head;
    }
}

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

https://leetcode.cn/problems/remove-nth-node-from-end-of-list/

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

上文讲链表基础时,我们是从头到尾遍历链表,删除的是第n个节点,现在要删除倒数第 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 dumy_head = new ListNode(0,head);
        ListNode fast_index = dumy_head;
        ListNode slow_index = dumy_head;
        // 将快指针领先 n 步
        for(int i=0;i<n;i++){
            fast_index = fast_index.next;
        }

        // 快慢指针一起出发
        while(fast_index.next!=null){
            slow_index = slow_index.next;
            fast_index = fast_index.next;
        }

        // 这时 slow_index 指向要删除节点的前一个节点
        ListNode rm_node = slow_index.next;
        slow_index.next = rm_node.next;
        rm_node.next = null;
        
        // 删除 dumy_head
        ListNode new_node = dumy_head.next;
        dumy_head.next = null;
        return new_node;

    }
}

反转链表

https://leetcode.cn/problems/reverse-linked-list/description/

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

比如 1->2->3->4->5->NULL 反转后 5->4->3->2->1->NULL‘

反转链表也可以使用快慢指针进行处理,将链表看作两部分,慢指针左半部分为新链表,慢指针右半部分为需要处理的节点:

⨳ 快指针用于检索,快指针指向的节点即为要插入新链表头部位置的节点

⨳ 慢指针用于记录,即慢指针指向新链表的头节点

既然对链表的操作是在头部插入节点,就不需要使用虚拟节点统一逻辑了:

/**
 * 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 reverseList(ListNode head) {
        if(head==null){
            return head;
        }

        ListNode slow_index = null;
        ListNode fast_index = head;
        while(fast_index!=null){
            // 记录 fast_index 下一个节点
            ListNode fast_index_next = fast_index.next;
            // 将 fast_index 指向的节点,插入新链表头部
            fast_index.next = slow_index;
            // 指针移动
            slow_index = fast_index;
            fast_index = fast_index_next;
        }

        return slow_index;
    }
}

链表相交

https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/description/

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

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

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

要注意,交点不是数值相等,而是指针相等,比如:

image.png

两个链表如果相交,那相交节点及其后边的节点一定是一样的。

那是不是可以使用双指针,让两个指针分别指向两个链表的尾节点,然后两个指针一起从后向前遍历,比较指针是否相同,最后一个相同的节点便是交点。

这样做是可以,但题中的链表是单向链表,从尾向头遍历不现实,那怎么办,先将链表反转吗?

题中要求链表必须 保持其原始结构 ,反转也不能用,怎么办?

我们真正的目的是让两个指针同步,这样才能比较交点,既然尾部对齐可以比较交点,那反过来,让两个链表长度一致,这样从头到尾遍历,也就相当于从尾向头遍历了,只不过一个是找第一个相同的节点,一个是找最后一个相同的节点。

让两个链表长度一致,不需要截取链表,仅仅让较长的链表先跑一下就可以了:

/**
 * 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 a_index = headA; // 指向A链表头节点的指针
        ListNode b_index = headB; // 指向B链表头节点的指针

        // 比较两个链表的长度,看看哪个指针先跑
        int a_size = 0;
        ListNode a_node= headA;
        while(a_node!=null){
            a_size++;
            a_node =a_node.next;
        }

        int b_size = 0;
        ListNode b_node = headB;
        while(b_node!=null){
            b_size++;
            b_node =b_node.next;
        }

        int gap = a_size-b_size; // 计算先跑多少步
        // A 链表比较长,那 a指针先跑
        while(gap>0){
           a_index = a_index.next;
           gap--;
        }
        // B 链表比较长,那 b指针先跑
        while(gap<0){
           b_index = b_index.next;
           gap++;
        }

        // 二者同时跑
        while(a_index !=null && b_index !=null ){
            if(a_index==b_index){
                return a_index;
            }
            a_index=a_index.next;
            b_index=b_index.next;
        }
        return null;
    }
}

环形链表

https://leetcode.cn/problems/linked-list-cycle/

给你一个链表的头节点 head ,判断链表中是否有环。

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

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

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

这道题就是判断链表的一部分是不是可以构成循环链表。咋判断呢?

还是快慢指针的思想,让快指针跑的快(比如一次遍历两个节点),慢指针跑的慢(比如一次遍历一个节点)。

快慢指针一旦进入环形链表,就会一直在环中循环,而且快指针肯定会比慢指针先进入环,等到慢指针进入环,二者会一起在环中循环,又因为快指针跑的快,所以它必会追上慢指针。

这里的追上,是指快指针和慢指针相遇,也就是快指针指向的节点等于慢指针指向的节点,那为啥一定能相遇。

这是因为快指针是走两步,慢指针是走一步,其实相对于慢指针来说,快指针是一个节点一个节点的靠近慢指针的,所以快指针一定可以和慢指针相遇。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast_index = head;
        ListNode slow_index = head;
        while(fast_index!=null && fast_index.next!=null){
            fast_index = fast_index.next.next;
            slow_index = slow_index.next;
            if(fast_index==slow_index)
                return true;
            
        }
        return false;
    }
}

环形链表II

https://leetcode.cn/problems/linked-list-cycle-ii/description/

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

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

不允许修改 链表。

这道题除了判断链表的一部分是不是可以构成循环链表,还要定位进入循环链表的第一个节点。

要解这道题就要列式子算术了:

假设从头节点到环形入口节点的节点数为x。 环形入口节点到快指针与慢指针相遇节点的节点数为y。 从相遇节点再到环形入口节点的节点数为 z

image.png

那么相遇时: 慢指针走过的节点数为: x + y, 快指针走过的节点数:x + y + n (y + z)n 为快指针在环内走了n圈才遇到慢指针, (y+z)为 一圈内节点的个数。

因为快指针是一步走两个节点,慢指针一步走一个节点, 所以快指针走过的节点数 = 慢指针走过的节点数 * 2: (x + y) * 2 = x + y + n (y + z),即 (x + y) = n (y + z),此为一个等式。

因为求的是从头节点到环形入口节点的节点数 x ,将 x 移到等式的左边,则 x = n (y + z) - y,再从 n(y+z) 中提出一个 (y+z)来,则 x = (n - 1) (y + z) + z

现在仔细分析这个式子:x = (n - 1) (y + z) + z

n 一定是大于等于 1 的,因为快指针至少要多走一圈才能相遇慢指针。

(y + z) 是环形链表的节点数

z 是相遇节点到环形入口节点的节点数

n 等于 1 时,表示快指针在环内走了一圈就遇到了慢指针,化简式子为 x=z,也就是从头节点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点

n 大于 1 时,表示快指针在环内走了n 圈才遇到慢指针,一样可以从头节点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点

/**
 * 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_index = head;
        ListNode slow_index = head;

        while(fast_index!=null && fast_index.next !=null){
            fast_index = fast_index.next.next;
            slow_index = slow_index.next;
            if(fast_index == slow_index ){
                ListNode meet_index = fast_index;// 相遇点
                ListNode new_index = head; // 重新从 head 节点出发的指针
                while(meet_index!=new_index){
                    meet_index = meet_index.next;
                    new_index = new_index.next;
                }
                return new_index;
            }
        }
        return null;
    }
}