当前位置: 首页 > news >正文

链表类力扣刷题总结

目录

一、链表基础原理

链表的分类

链表的特性

二、核心解题方法

1. 双指针法

2. 递归法

3. 哈希表法

4. 哑节点(虚拟头节点)

三、经典题型分类与代码实现

(一)基础操作类

1. 反转链表(LeetCode 206)

2. 合并两个有序链表(LeetCode 21)

3. 删除链表的倒数第 N 个节点(LeetCode 19)

(二)环与相交类

1. 环形链表(LeetCode 141)

2. 环形链表 II(LeetCode 142)

3. 相交链表(LeetCode 160)

(三)回文与对称类

回文链表(LeetCode 234)

(四)排序与复杂重组类

1. 排序链表(LeetCode 148)

2. K 个一组翻转链表(LeetCode 25)

3. 随机链表的复制(LeetCode 138)

(五)数值计算类

两数相加(LeetCode 2)

(六)困难拓展类:合并 K 个升序链表(LeetCode 23)

总结


一、链表基础原理

链表是一种线性数据结构,由 ** 节点(Node)** 组成,每个节点包含两部分:

  • 数据域:存储节点的值(如 int val)。
  • 指针域:存储下一个节点的引用(如 ListNode next)。
链表的分类
  • 单链表:每个节点只有一个指向下一节点的指针,尾节点指向 null
  • 双链表:每个节点有两个指针(prev 指向前一节点,next 指向后一节点)。
  • 循环链表:尾节点指向头节点,形成 “环”。
链表的特性
  • 插入 / 删除高效:已知位置时,时间复杂度为 O(1)(仅需修改指针)。
  • 访问低效:访问第 k 个节点需遍历,时间复杂度 O(n)

二、核心解题方法

1. 双指针法
  • 快慢指针:快指针速度是慢指针的 2 倍(或其他倍数),用于:
    • 找链表中点(如回文链表、排序链表的归并)。
    • 判断环形链表(相遇则有环)。
    • 找环形链表的入环点(相遇后,一个指针回 head,同速移动)。
  • 前后指针:两个指针一前一后,用于:
    • 反转链表prev 和 curr 配合修改指针方向)。
    • 删除倒数第 N 个节点(快指针先走 N 步,再一起走)。
2. 递归法

递归的本质是将大问题拆解为相同逻辑的子问题,适用于:

  • 反转链表(反转剩余部分,再连接当前节点)。
  • 合并有序链表(比较头节点,递归合并剩余部分)。
3. 哈希表法

用于记录节点的 “访问状态” 或 “映射关系”,适用于:

  • 判断环形链表(记录已访问节点,重复则有环)。
  • 相交链表(记录 A 链表节点,遍历 B 时判断是否存在)。
  • 随机链表的复制(记录原节点到新节点的映射,处理 random 指针)。
4. 哑节点(虚拟头节点)

当链表的头节点操作需要特殊处理时(如合并链表时头节点不确定、删除头节点),创建一个 val 无意义的节点作为头节点的前驱,简化逻辑。通常是dummy~

三、经典题型分类与代码实现

(一)基础操作类
1. 反转链表(LeetCode 206)

问题:反转单链表(如 1->2->3->null → 3->2->1->null)。

方法:双指针或递归。

注:此处的null也可以是其他节点;用于反转部分节点

// 双指针法
public ListNode reverseList(ListNode head) {// prev通常是链表尾的指向ListNode prev = null;// 当前节点,后面逐渐后移,至nullListNode curr = head;while (curr != null) {//保存当前节点的下一个节点,避免后续当前节点的移动ListNode nextTemp = curr.next;//将当前指向prev表尾curr.next = prev;// prev指针左移/前移prev = curr;//当前指针后移,进行下一轮交换curr = nextTemp;}//最终prev从最右端移到了最左端,成为了头节点return prev;
}// 递归法
public ListNode reverseList(ListNode head) {// 递归终止条件if (head == null || head.next == null) return head;//1->2->3->4->nullListNode newHead = reverseList(head.next);head.next.next = head;head.next = null;return newHead;
}/**
递归处理 “剩余部分”ListNode newHead = reverseList(head.next);
以链表 1->2->3->4->null 为例,递归调用会不断深入:
第一次调用:head=1,处理 head.next=2 → 进入下一层递归。
第二次调用:head=2,处理 head.next=3 → 进入下一层递归。
第三次调用:head=3,处理 head.next=4 → 进入下一层递归。
第四次调用:head=4,满足 head.next == null → 触发终止条件,返回 4(此时 newHead=4)。
步骤 3:调整 “当前节点” 与 “后继” 的指针head.next.next = head;
head.next = null;
回到第三次调用(head=3,newHead=4):
head.next 是 4,所以 head.next.next = head → 等价于 4.next = 3,此时链表变为 3<->4。
head.next = null → 断开 3->4 的旧连接,最终 3 的 next 为 null,链表变为 4->3->null。
返回 newHead=4 → 回到第二次调用。
回到第二次调用(head=2,newHead=4):
head.next 是 3,所以 3.next = 2 → 链表变为 4->3->2。
head.next = null → 断开 2->3 的旧连接,2 的 next 为 null,链表变为 4->3->2->null。
返回 newHead=4 → 回到第一次调用。
回到第一次调用(head=1,newHead=4):
head.next 是 2,所以 2.next = 1 → 链表变为 4->3->2->1。
head.next = null → 断开 1->2 的旧连接,1 的 next 为 null,链表变为 4->3->2->1->null。
返回 newHead=4 → 最终整个链表完成反转。**/
2. 合并两个有序链表(LeetCode 21)

问题:将两个升序链表合并为一个升序链表。

方法:双指针 + 哑节点。

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {// 设置哑节点,因为两个头节点都要被处理ListNode dummy = new ListNode(0);// 设置当前节点,后续不断后移ListNode prev = dummy;// 当两个链表都不为空时继续合并while (list1 != null && list2 != null) {// 将值更小的节点优先加入,同时移动结果链表和当前链表if (list1.val <= list2.val) {// 实现连接prev.next = list1;list1 = list1.next;} else {prev.next = list2;list2 = list2.next;}prev = prev.next;}// 哪个表空了就直接连上,注意是在循环结束后prev.next = list1 != null ? list1 : list2;// 返回哑节点的next,刚好是头节点return dummy.next;
}
3. 删除链表的倒数第 N 个节点(LeetCode 19)

问题:删除链表的倒数第 n 个节点(如 1->2->3->4->5n=2 → 1->2->3->5)。

方法:快慢指针 + 哑节点(快指针先走 n 步,再一起走,找到待删除节点的前驱)。

public ListNode removeNthFromEnd(ListNode head, int n) {// 哑节点构造的简化写法,和 “先 new 哑节点再设置dummy.next = head” 效果完全一致// ListNode dummy = new ListNode(0); dummy.next = head;ListNode dummy = new ListNode(0, head);// 初始化,都先在哑节点启动ListNode fast = dummy, slow = dummy;// 快节点先走n+1步(此处如果是i<n就说多走n步,while循环条件需要改为fast.next!=null)for (int i = 0; i <= n; i++) fast = fast.next;while (fast != null) {// 两个指针同时移动,最终fast到达null,slow比fast落后n+1个位置,在需要删除的节点前驱fast = fast.next;slow = slow.next;}// 此处对第n个节点进行删除slow.next = slow.next.next;//返回头节点return dummy.next;
}

(二)环与相交类
1. 环形链表(LeetCode 141)

问题:判断链表是否有环。

方法:快慢指针(快指针走 2 步,慢指针走 1 步,相遇则有环)。

public boolean hasCycle(ListNode head) {if (head == null || head.next == null) return false;ListNode slow = head, fast = head;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) return true;}return false;
}

注:哈希表法也可以解决

    public ListNode removeNthFromEnd(ListNode head, int n) {if (head == null) {return null;}// 第一步:遍历链表,用Map记录每个节点的索引和节点本身Map<Integer, ListNode> indexToNode = new HashMap<>();ListNode current = head;int length = 0; // 链表总长度while (current != null) {indexToNode.put(length, current); // 索引从0开始current = current.next;length++;}// 第二步:计算要删除的节点的索引(倒数第n个 = 正数第 length - n 个)int deleteIndex = length - n;// 特殊情况:删除的是头节点(索引0)if (deleteIndex == 0) {return head.next;}// 第三步:找到目标节点的前一个节点,删除目标节点ListNode prevNode = indexToNode.get(deleteIndex - 1); // 前一个节点的索引prevNode.next = prevNode.next.next; // 跳过目标节点return head;
2. 环形链表 II(LeetCode 142)

问题:返回环形链表的入环第一个节点。

方法:快慢指针相遇后,一个指针回 head,同速移动,相遇处为入环点。

public ListNode detectCycle(ListNode head) {if (head == null || head.next == null) return null;ListNode slow = head, fast = head;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) { // 相遇,存在环ListNode ptr = head;while (ptr != slow) {ptr = ptr.next;slow = slow.next;}return ptr; // 入环点}}return null;
}
3. 相交链表(LeetCode 160)

问题:找到两个单链表相交的起始节点(不相交返回 null)。

方法:双指针(A 走完走 B,B 走完走 A,相遇则相交)。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA == null || headB == null) return null;ListNode pA = headA, pB = headB;while (pA != pB) {pA = pA == null ? headB : pA.next;pB = pB == null ? headA : pB.next;}return pA;
}
(三)回文与对称类
回文链表(LeetCode 234)

问题:判断链表是否为回文(如 1->2->2->1 是回文,1->2 不是)。

方法:找中点 + 反转后半段 + 比较。

public boolean isPalindrome(ListNode head) {if (head == null || head.next == null) return true;// 找中点ListNode slow = head, fast = head;while (fast.next != null && fast.next.next != null) {slow = slow.next;fast = fast.next.next;}// 反转后半段ListNode reversedHalf = reverseList(slow.next);// 比较前半段和反转后的后半段ListNode p1 = head, p2 = reversedHalf;while (p2 != null) {if (p1.val != p2.val) return false;p1 = p1.next;p2 = p2.next;}return true;
}private ListNode reverseList(ListNode head) {ListNode prev = null, curr = head;while (curr != null) {ListNode nextTemp = curr.next;curr.next = prev;prev = curr;curr = nextTemp;}return prev;
}
(四)排序与复杂重组类
1. 排序链表(LeetCode 148)

问题:对链表进行升序排序。

方法:归并排序(找中点 + 递归合并)。

public ListNode sortList(ListNode head) {if (head == null || head.next == null) return head;// 找中点ListNode slow = head, fast = head.next;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}ListNode mid = slow.next;slow.next = null; // 拆分链表// 递归排序左右两部分ListNode left = sortList(head);ListNode right = sortList(mid);// 合并ListNode dummy = new ListNode(0);ListNode prev = dummy;while (left != null && right != null) {if (left.val <= right.val) {prev.next = left;left = left.next;} else {prev.next = right;right = right.next;}prev = prev.next;}prev.next = left != null ? left : right;return dummy.next;
}
2. K 个一组翻转链表(LeetCode 25)

问题:每 k 个节点一组进行翻转(如 1->2->3->4->5k=2 → 2->1->4->3->5)。

方法:分组处理,每组内部反转,再拼接。

public ListNode reverseKGroup(ListNode head, int k) {ListNode dummy = new ListNode(0, head);ListNode prev = dummy;while (true) {// 检查剩余节点是否足够k个ListNode tail = prev;for (int i = 0; i < k; i++) {tail = tail.next;if (tail == null) return dummy.next;}ListNode nextGroup = tail.next;// 反转当前k个节点ListNode[] reversed = reverse(prev.next, tail);ListNode newHead = reversed[0], newTail = reversed[1];// 拼接prev.next = newHead;newTail.next = nextGroup;prev = newTail;}
}// 反转从head到tail的链表,返回新的头和尾
private ListNode[] reverse(ListNode head, ListNode tail) {ListNode prev = tail.next, curr = head;while (prev != tail) {ListNode nextTemp = curr.next;curr.next = prev;prev = curr;curr = nextTemp;}return new ListNode[]{tail, head};
}
3. 随机链表的复制(LeetCode 138)

问题:复制一个带有 random 指针的链表(random 可能指向任意节点或 null)。

方法:哈希表记录原节点到新节点的映射,先复制 next 指针,再处理 random 指针。

public Node copyRandomList(Node head) {if (head == null) return null;Map<Node, Node> map = new HashMap<>();Node curr = head;// 复制节点,存储映射while (curr != null) {map.put(curr, new Node(curr.val));curr = curr.next;}curr = head;// 处理next和random指针while (curr != null) {map.get(curr).next = map.get(curr.next);map.get(curr).random = map.get(curr.random);curr = curr.next;}return map.get(head);
}
(五)数值计算类
两数相加(LeetCode 2)

问题:两个非空链表表示两个非负整数(每位数字逆序存储),求它们的和。

方法:模拟加法,注意进位,哑节点存储结果。

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode curr = dummy;int carry = 0;while (l1 != null || l2 != null || carry != 0) {int sum = carry;if (l1 != null) {sum += l1.val;l1 = l1.next;}if (l2 != null) {sum += l2.val;l2 = l2.next;}carry = sum / 10;curr.next = new ListNode(sum % 10);curr = curr.next;}return dummy.next;
}
(六)困难拓展类:合并 K 个升序链表(LeetCode 23)

问题:合并 k 个升序链表,返回合并后的升序链表。

方法:优先队列(最小堆)或分治合并。

// 方法1:优先队列(最小堆)
public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) return null;PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);for (ListNode list : lists) {if (list != null) pq.offer(list);}ListNode dummy = new ListNode(0);ListNode curr = dummy;while (!pq.isEmpty()) {ListNode node = pq.poll();curr.next = node;curr = curr.next;if (node.next != null) pq.offer(node.next);}return dummy.next;
}// 方法2:分治合并
public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) return null;return merge(lists, 0, lists.length - 1);
}private ListNode merge(ListNode[] lists, int left, int right) {if (left == right) return lists[left];int mid = left + (right - left) / 2;ListNode l1 = merge(lists, left, mid);ListNode l2 = merge(lists, mid + 1, right);return mergeTwoLists(l1, l2);
}private ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode prev = dummy;while (l1 != null && l2 != null) {if (l1.val <= l2.val) {prev.next = l1;l1 = l1.next;} else {prev.next = l2;l2 = l2.next;}prev = prev.next;}prev.next = l1 != null ? l1 : l2;return dummy.next;
}

总结

链表题的核心在于指针的灵活操作。需掌握 “双指针、递归、哈希表、哑节点” 这四类方法,将大部分题型拆解为 “模板化” 的逻辑。

  • 环和相交问题:快慢指针的 “相遇逻辑”。
  • 反转和合并:双指针 + 哑节点的 “拼接模板”。
  • 复杂重组(如 K 个一组、排序):将问题拆分为 “子问题”(递归或分组),再逐一解决。

http://www.dtcms.com/a/566764.html

相关文章:

  • 网站建设工作室怎么开茂名网站开发公司
  • 衡阳建设学校官方网站广东省住房和城乡建设厅证件查询
  • 厦门做网站seo的网站应该如何进行优化
  • 白山商城网站建设wordpress默认原始图片
  • 做质量计量的网站有哪些博乐建设工程信息网站
  • 【Transformer系列(2)】注意力机制、自注意力机制、多头注意力机制、通道注意力机制、空间注意力机制超详细讲解
  • 东莞网站建设推广方案网站主题设计特色
  • 代做毕业设计网站有哪些网络整合营销推广
  • 嵌入式笔记系列——IIC
  • 网站维护一年一般多少钱视频链接生成器
  • wordpress网站好用吗专业的营销型网站定制
  • 离线推广网站规划书电子商务网站开发相关技术
  • 直接保存pandas DataFrame的内容到Excel文件中
  • excel T检测时[检验类型]参数设置的方法
  • 网站尾部做淘宝客可以有高佣金的网站
  • 成都网站建设四川推来客网络石家庄+外贸网站建设公司
  • 网站开发人员岗位wordpress调查插件
  • Python判断语句
  • 网站如何做微信登录网站顶部代码
  • 织梦网站首页模板路径广告平面设计好学吗
  • app开发网站开发成都较出名的广告公司
  • 可以直接打开网站的方法运城网站推广
  • 站内推广有哪些方式云南测绘公司最新排名
  • 常州便宜的做网站服务seo渠道
  • Linux基础常用命令
  • 西安网站建设产品浙江省建设信息港官网首页
  • VirtualBox 搭建 ubuntu
  • 东台网站建设服务商驾校报名网站怎么做
  • 美团开源啦,源码地址+部署脚本,全模态实时交互
  • SFT 和 RL 融合:CHROD, LUFFY,UFT