链表相关题目

两两交换链表中的节点

leetcode.cn/problems/sw...

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

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

java 复制代码
/**
 * 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个节点

leetcode.cn/problems/re...

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

上文讲链表基础时,我们是从头到尾遍历链表,删除的是第n个节点,现在要删除倒数第 n 个节点,题中的链表又不是双向链表,链表的长度也没有给出。

难道需要从头到尾遍历两次,第一次遍历记录倒数第 n 个节点是正数第几个节点,第二次遍历在进行删除?

两次遍历可以是可以,但如果用到数组篇讲的快慢指针,一次遍历也是可以的。

快指针一般用于检索,那就让快指针比慢指针提前走 n 步,当快指针走到尾节点时,慢指针指向的节点就是要删除节点的前一个节点。

为了统一对头节点的操作,还是设置虚拟节点进行操作:

java 复制代码
/**
 * 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;

    }
}

反转链表

leetcode.cn/problems/re...

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

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

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

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

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

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

java 复制代码
/**
 * 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;
    }
}

链表相交

leetcode.cn/problems/in...

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

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

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

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

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

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

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

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

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

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

java 复制代码
/**
 * 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;
    }
}

环形链表

leetcode.cn/problems/li...

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

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

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

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

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

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

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

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

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

java 复制代码
/**
 * 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

leetcode.cn/problems/li...

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

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

不允许修改 链表。

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

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

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

那么相遇时: 慢指针走过的节点数为: 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 圈才遇到慢指针,一样可以从头节点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点

java 复制代码
/**
 * 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;
    }
}
相关推荐
业精于勤的牙21 分钟前
三角形最小路径和(二)
算法
风筝在晴天搁浅22 分钟前
hot100 239.滑动窗口最大值
数据结构·算法·leetcode
夏乌_Wx34 分钟前
练题100天——DAY31:相对名次+数组拆分+重塑矩阵
数据结构·算法
LYFlied34 分钟前
【算法解题模板】-解二叉树相关算法题的技巧
前端·数据结构·算法·leetcode
Ven%1 小时前
【AI大模型算法工程师面试题解析与技术思考】
人工智能·python·算法
天勤量化大唯粉1 小时前
枢轴点反转策略在铜期货中的量化应用指南(附天勤量化代码)
ide·python·算法·机器学习·github·开源软件·程序员创富
爱学习的小仙女!1 小时前
算法效率的度量 时间复杂度 空间复杂度
数据结构·算法
AndrewHZ1 小时前
【复杂网络分析】什么是图神经网络?
人工智能·深度学习·神经网络·算法·图神经网络·复杂网络
Swizard1 小时前
拒绝“狗熊掰棒子”!用 EWC (Elastic Weight Consolidation) 彻底终结 AI 的灾难性遗忘
python·算法·ai·训练
fab 在逃TDPIE2 小时前
Sentaurus TCAD 仿真教程(十)
算法