【Leetcode】链表

news/2025/1/5 5:32:48/文章来源:https://www.cnblogs.com/syr463/p/18642430

链表

160. 相交链表

206. 反转链表

234. 回文链表

141. 环形链表

142. 环形链表 II

21. 合并两个有序链表

2. 两数相加

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

25. K 个一组翻转链表

138. 随机链表的复制

148. 排序链表

23. 合并 K 个升序链表

146. LRU 缓存

160. 相交链表

方法一:模拟

依次判断两节点是否相同即可

public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {ListNode nodeA = headA;ListNode nodeB = headB;int lenA = 0, lenB = 0;while(nodeA.next != null) {lenA++;nodeA = nodeA.next;}while(nodeB.next != null) {lenB++;nodeB = nodeB.next;}int diffLen = lenA>=lenB ? lenA-lenB : lenB-lenA;// 保证 A链表 比 B链表 长if(lenA < lenB) {ListNode temp = headA;headA = headB;headB = temp;}while(diffLen-- > 0) {headA = headA.next;}while(headA != null) {if(headA == headB) {return headA;}headA = headA.next;headB = headB.next;}return null;}
}

方法二:快慢指针,消除步长差

图解相交链表

  1. 假设链表A为 ***** x #####,链表B为**** x #####,相交点为x,相交部分为x #####;则链表A中各部分的长度为a(*)+a(1)+a(#),链表B中各部分的长度为b(*)+b(1)+b(#),由上可知a(*)!=b(*),但a(#)=b(#)
  2. 使用pA指针先遍历A再遍历B,到相交点的长度为:a(*)+a(1)+a(#) + b(*)+b(1)
  3. 使用pB指针先遍历B再遍历A,到相交点的长度为:b(*)+b(1)+b(#) + a(*)+a(1)
  4. 那么a(*)+a(1)+a(#) + b(*)+b(1) b(*)+b(1)+b(#) + a(*)+a(1) 的关系是怎样的?
    1. 已知a(*)!=b(*),但a(#)=b(#)
    2. 则,a(*)+a(1)+a(#) + b(*)+b(1) = a(*)+a(1)+b(#) + b(*)+b(1) = a(*)+a(1)+ b(#)+ b(*)+b(1) = b(*)+b(1)+b(#) + a(*)+a(1)
public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if(headA == null || headB == null) {return null;}ListNode pA = headA, pB = headB;while(pA != pB) {// 当pA==null时,说明遍历到了A的末尾,从B开始遍历pA = pA==null ? headB : pA.next;// 当pB==null时,说明遍历到了B的末尾,从A开始遍历pB = pB==null ? headA : pB.next;}return pA;}
}

206. 反转链表

方法一:迭代

class Solution {public ListNode reverseList(ListNode head) {ListNode pre=null, cur=head, temp=null;while(cur != null){temp = cur.next;cur.next = pre;pre = cur;cur = temp;}return pre;}
}

方法二:递归

class Solution {public ListNode reverseList(ListNode head) {return reverse(null, head);}private ListNode reverse(ListNode pre, ListNode cur) {if(cur == null) {return pre;}ListNode temp = null;temp = cur.next;cur.next = pre;return reverse(cur, temp);}
}

234. 回文链表

方法一:快慢指针

/*** 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 boolean isPalindrome(ListNode head) {if(head == null) {return true;}// 快慢指针,快指针移动到末尾时,慢指针所指位置是中间节点ListNode fast = head, slow = head;while(fast != null) {fast = fast.next;if(fast != null) {fast = fast.next;}slow = slow.next;}// 翻转后半段的链表ListNode node = reverse(slow);// 比较 经过反转后的后半段链表 和 前半段的链表while(node != null) {if(head.val != node.val) {return false;}head = head.next;node = node.next;}return true;}// 反转链表private ListNode reverse(ListNode cur) {ListNode pre = null, post = null;while(cur != null) {post = cur.next;cur.next = pre;pre = cur;cur = post;}return pre;}
}

141. 环形链表

方法一:快慢指针

利用快慢指针判断当前链表中是否含有换,如果两个指针相遇,则说明链表中有环

public class Solution {public boolean hasCycle(ListNode head) {ListNode fast = head, slow = head;// 排除只含有一个元素,且无环的情况,例如例三head=[1]if(head == null || head.next == null){return false;}// 快慢指针,快指针移两格,慢指针移一格,两指针相遇则说明存在环while(fast != null) {fast = fast.next;if(fast != null) {fast = fast.next;}slow = slow.next;// 放在移动后判断,因为一开始指针指向同一位置if(fast == slow) {return true;}}return false;}
}

142. 环形链表 II

方法一:快慢指针

fig1

  1. 判断是否存在环(快慢指针在环中相遇)
  2. 判断入环的位置,由上图,
    1. 快指针走过的位置为a+n*(b+c) + b,n为走过的环的圈数;慢指针走过的距离为a+b
    2. 快指针每次移动两步,慢指针每次移动一步
    3. 则有:a+n*(b+c) + b = 2*(a+b),化简:a = (n-1)*(b+c) + c
    4. 因此,一个指针从head开始走a步到入环点,另一个指针在环中走n-1圈+相遇点到入环点的距离;两者相等
public class Solution {public ListNode detectCycle(ListNode head) {ListNode fast = head, slow = head;while(fast!=null && fast.next!=null) {fast = fast.next.next;slow = slow.next;// 快慢指针相遇,存在环if(fast == slow) {// 寻找环中的第一个节点ListNode index1 = head;ListNode index2 = fast;while(index1 != index2) {index1 = index1.next;index2 = index2.next;}return index1;}}return null;}
}

21. 合并两个有序链表

方法一:迭代

  1. 定义变量:temp用于记录已合并链表的最大元素,list1list2指向两个未合并链表的头元素,head记录已合并的连链表头元素
  2. 依次比较list1list2所指向元素的值,将temp指向两者中的较小一个,并更新指针temp以及list1 / list2
  3. list1 / list2遍历到结尾时,将另一链表的剩余元素插入即可
class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {if(list1==null) return list2;if(list2==null) return list1;ListNode temp = null;if(list1.val < list2.val) {temp = list1;list1 = list1.next;} else {temp = list2;list2 = list2.next;}ListNode head = temp;// System.out.println(list1.val + "," + list2.val);// 指针变化条件:两指针所指位置均不为空while(!(list1==null || list2==null)) {if(list1.val < list2.val) {temp.next = list1;list1 = list1.next;} else {temp.next = list2;list2 = list2.next;}temp = temp.next;}// 其中一个指针所指位置为空,则将另一链表的剩余分布添加到当前链表中即可if(list1==null) {temp.next = list2;} else {temp.next = list1;}return head;}}

方法二:迭代(化简)

class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {// 设置fakehead,返回值为fakehead.next,不用关心temp究竟指向list1还是list2ListNode fakehead = new ListNode(-1);ListNode temp = fakehead;// 指针变化条件:两指针所指位置均不为空while(!(list1==null || list2==null)) {if(list1.val < list2.val) {temp.next = list1;list1 = list1.next;} else {temp.next = list2;list2 = list2.next;}temp = temp.next;}// 其中一个指针所指位置为空,则将另一链表的剩余分布添加到当前链表中即可temp.next = list1==null ? list2 : list1;return fakehead.next;}}

2. 两数相加

方法一:模拟

  1. 题目要求,将两个链表对应的元素相加sum,并将其个位sum%10存到一个新的ListNode中,同时应该注意进位carry=sum/10
  2. 初始变量,使用headtail记录新链表的首尾元素,插入第一个元素时更新首尾指针;之后,一直更新尾指针。
  3. 最后,计算完成后,记得判断最后一次计算是否有进位,如有则将该数据存入链表
class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode head = null, tail = null;// 进位int carry = 0;while(l1 != null || l2 != null) {int n1 = l1!=null ? l1.val : 0;int n2 = l2!=null ? l2.val : 0;int sum = n1 + n2 + carry;// 处理首尾节点if(head == null) {// 头节点,记录首元素head = tail = new ListNode(sum % 10);} else {// 剩余节点使用tail标记,并更新tail指针tail.next = new ListNode(sum % 10);tail = tail.next;}carry = sum / 10;if(l1 != null) l1 = l1.next;if(l2 != null) l2 = l2.next;}// 更新最后一个进位元素,如果最高位存在进位,则需要单另创建一个节点更新该值,如示例三if(carry > 0) {tail.next = new ListNode(carry);}return head;}
}

方法二:模拟

  1. 与前述方法类似,只是用fakehead标记头节点,采用temp指针更新元素
  2. 注意:while(l1!=null || l2!=null)while(!(l1==null || l1==null))的区别
class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode fakehead = new ListNode(-1);ListNode temp = fakehead;int add = 0;while(l1!=null || l2!=null) {int n1 = l1!=null ? l1.val : 0;int n2 = l2!=null ? l2.val : 0;int sum = n1 + n2 + add;temp.next = new ListNode(sum % 10);temp = temp.next;add = sum / 10;if(l1 != null) l1 = l1.next;if(l2 != null) l2 = l2.next;}if(add > 0) {temp.next = new ListNode(add);}return fakehead.next;}
}

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

方法一:双指针

  1. 创建fakehead,对于删除头结点的情况不用分类讨论
  2. 注意rightleft的遍历位置,left需要停到待删除位置的前一个位置,不然无法通过left.next = left.next.next修改指针指向
class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {// 创建fakehead,对于删除头结点的情况不用分类讨论ListNode fakehead = new ListNode(-1);fakehead.next = head;ListNode left = fakehead, right = fakehead;for(int i=0; i<n; i++) {right = right.next;}while(right.next != null) {right = right.next;left = left.next;}left.next = left.next.next;return fakehead.next;}
}

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

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

方法一:模拟,双指针

使用firstNodesecondNode记录待交换的两个元素,cur为待交换元素的前一个元素,依次模拟指针的变化即可

class Solution {public ListNode swapPairs(ListNode head) {ListNode fakehead = new ListNode(-1);fakehead.next = head;ListNode cur = fakehead;ListNode firstNode = null, secondNode = null;while(cur.next!=null && cur.next.next!=null) {firstNode = cur.next;secondNode = cur.next.next;cur.next = secondNode;firstNode.next = secondNode.next;secondNode.next = firstNode;cur = firstNode;}return fakehead.next;}
}

25. K 个一组翻转链表

方法一:模拟

class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode fakehead = new ListNode(-1);fakehead.next = head;ListNode pre = fakehead;while(head != null) {ListNode tail = pre;// 剩余元素不足k个,不进行翻转,将结果直接返回即可for(int i=0; i<k; i++) {tail = tail.next;if(tail == null) {return fakehead.next;}}// newhead 为新一组元素首节点ListNode newhead = tail.next;ListNode[] reverse = myReverse(head, tail);head = reverse[0];tail = reverse[1];pre.next = head;tail.next = newhead;pre = tail;head = pre.next;}return fakehead.next;}private ListNode[] myReverse(ListNode head, ListNode tail) {ListNode prev = tail.next;ListNode p = head;while(prev != tail) {ListNode newhead = p.next;p.next = prev;prev = p;p = newhead;}return new ListNode[]{tail, head};}
}

138. 随机链表的复制

方法一:迭代,哈希表

https://leetcode.cn/problems/copy-list-with-random-pointer/description/comments/2247988

class Solution {public Node copyRandomList(Node head) {Node fakehead = new Node(-1);Node p = head, q = fakehead;HashMap<Node, Node> map = new HashMap<>();// 第一次遍历,新建链表节点,并将p中的val值复制到新结点中while(p != null) {q.next = new Node(p.val);q = q.next;map.put(p, q);p = p.next;}// 第二次遍历,建立random的映射,从map中根据p.random取出节点,并作为q.randomp = head;q = fakehead.next;while(p != null) {if(p.random != null) {q.random = map.get(p.random);}p = p.next;q = q.next;}return fakehead.next;       }
}

148. 排序链表

方法一:遍历+替换

遍历一遍链表中的元素值,并记录在ArrayList中;之后利用Collections.sort(list)对当前ArrayList中的数字排序;最后,遍历数组元素,将元素值进行替换

注意,使用该思路实现并不符合题意!!!

class Solution {public ListNode sortList(ListNode head) {ListNode fakehead = new ListNode(-1);fakehead.next = head;List<Integer> list = new ArrayList<>();while(head != null) {list.add(head.val);head = head.next;}Collections.sort(list);head = fakehead.next;for(int temp : list) {head.val = temp;head = head.next;}return fakehead.next;}
}

23. 合并 K 个升序链表

方法一:两两合并

依次将前一链表合并到后后一链表中,并将后一链表的头节点记录下来;重复上述过程,即可完成链表的合并;最后,返回值因为ListNode[]中的最后一个元素,即为最后一个链表的头节点。

链表两两合并,可参考上述21. 合并两个有序链表

class Solution {// 合并K个链表public ListNode mergeKLists(ListNode[] lists) {int len = lists.length;if(len == 0) return null;if(len == 1) return lists[0];for(int i=0; i<len-1; i++) {// 合并两链表,将前一链表的结果合并到后一链表中lists[i+1] = mergeList(lists[i], lists[i+1]);}// 最后合并后的结果为最后一个链表的头节点return lists[len-1];}// 合并两个链表private ListNode mergeList(ListNode list1, ListNode list2) {ListNode fakehead = new ListNode(-1);ListNode temp = fakehead;while(list1!=null && list2!=null) {if(list1.val < list2.val) {temp.next = list1;list1 = list1.next;} else {temp.next = list2;list2 = list2.next;}temp = temp.next;}temp.next = list1==null ? list2 : list1;return fakehead.next;}
}

方法二:分治法

将一个ListNode[]数组一分为二,合并前半部分和右半部分;依次递归执行

mid = (left + right) >> 1(left+right)/2,对于leftright较大的情况,可防止数据溢出。也可写为left+(right-left)/2

class Solution {// 合并K个链表public ListNode mergeKLists(ListNode[] lists) {int len = lists.length;if(len == 0) return null;if(len == 1) return lists[0];return merge(lists, 0, len-1);}private ListNode merge(ListNode[] lists, int left, int right) {if(left == right) return lists[left];if(left > right) return null;int mid = (left + right) >> 1;// 分治法 合并return mergeList(merge(lists, left, mid), merge(lists, mid+1, right));}// 合并两个链表【和前述方法中的两两合并相同】private ListNode mergeList(ListNode list1, ListNode list2) {ListNode fakehead = new ListNode(-1);ListNode temp = fakehead;while(list1!=null && list2!=null) {if(list1.val < list2.val) {temp.next = list1;list1 = list1.next;} else {temp.next = list2;list2 = list2.next;}temp = temp.next;}temp.next = list1==null ? list2 : list1;return fakehead.next;}
}

方法三:优先队列

【待补充】

146. LRU 缓存

方法一:双向链表 + 哈希表

  1. 定义四个函数addToHaed, removeNode, moveToHead, removeTail用于处理双端队列中节点移动情况(指针变化情况)
  2. 设置fakehead以及faketail来初始化双向链表
  3. 使用HashMap(cache)记录当前链表中存在的元素
class LRUCache {class DLinkedNode {int key;int value;DLinkedNode prev;DLinkedNode next;public DLinkedNode() {};public DLinkedNode(int _key, int _value) {key = _key;value = _value;}}private Map<Integer, DLinkedNode> cache = new HashMap<>();private int size;private int capacity;private DLinkedNode fakehead, faketail;public LRUCache(int capacity) {this.size = 0;this.capacity = capacity;fakehead = new DLinkedNode();faketail = new DLinkedNode();fakehead.next = faketail;faketail.next = fakehead;}public int get(int key) {DLinkedNode node = cache.get(key);if(node == null) {return -1;}// 访问该元素,将该元素移动到双端列表头节点,表示最近访问moveToHead(node);return node.value;}public void put(int key, int value) {DLinkedNode node = cache.get(key);if(node == null) {DLinkedNode newNode = new DLinkedNode(key, value);cache.put(key, newNode);addToHaed(newNode);size++;// 当前元素值超过了设置容量,将双端列表的最后一个元素(最久为访问)删除if(size > capacity) {DLinkedNode tail = removeTail();cache.remove(tail.key);size--;}} else {// 访问了当前元素,将该元素移动到双端列表首位,表示最近访问node.value = value;moveToHead(node);}}// 头插法private void addToHaed(DLinkedNode node) {node.prev = fakehead;node.next = fakehead.next;fakehead.next.prev = node;fakehead.next = node;}// 移除节点,只需要将节点的前后两个指针分别指向前后元素即可private void removeNode(DLinkedNode node) {node.prev.next = node.next;node.next.prev = node.prev;}// 将节点移动到头元素,删除-插入private void moveToHead(DLinkedNode node) {removeNode(node);addToHaed(node);}// 移除节点private DLinkedNode removeTail() {DLinkedNode res = faketail.prev;removeNode(res);return res;}}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/861568.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

The end-软工实践之旅

一、学期回顾 1.1 回顾你对于软件工程课程的想象 在学期初,我眼中的软件工程课程是一门教大家如何从零开始开发软件,实际上大部分知识都需要自己学习掌握,软工课着重系统地学习软件开发流程,掌握从需求分析、设计、编码到测试的全套本领,雕琢出实用且受欢迎的软件作品。 如…

‍禁止蕉绿——软件工程个人总结

这个作业属于哪个课程 软件工程这个作业要求在哪里 软件工程2024秋——个人总结作业这个作业的目标 学期总结学号 102202150🙅‍禁止焦虑——2024秋软工实践个人总结博客 一、🎞️学期回顾🎞️ 1.1 💭回顾你对于软件工程课程的想象💭 刚开始我对软件工程的理解只局限…

《计算机组成及汇编语言原理》阅读笔记:p133-p159

《计算机组成及汇编语言原理》学习第 11 天,p133-p159 总结,总计 27 页。 一、技术总结 1.segment (1)定义 Broadly speaking, a contiguous section of memory. More specifically, a section of memory referenced by one of the segment registers of the 80x86 family.…

并非银河战舰,皮划艇也能启航

目录标题一、学期回顾1.1 回顾对软工课程的想象1.2 回顾在这门课程中的投入与产出1.3 最印象深刻的答辩二、总结收获2.1 自己的人月神话2.2 学习到的新技术和生产力工具2.3 除了技术之外,还得到了哪些提升2.4 对下一届实践的建议三、致谢这个作业属于哪个课程 https://edu.cnb…

GitHub Copilot免费上线!快速上手指南与功能解析

GitHub Copilot在12月宣布免费订阅,不再限制学生和开源项目的维护者了,对于习惯白嫖的开发者来说无疑多了一个选择。 价格和功能 免费版目前可以选择Claude 3.5 Sonnet或GPT-4o模型,每月可以调用 2000 次生成和补全代码,以及 50 次聊天信息。快速开始注册一个GIthub账号 VS…

C#/.NET/.NET Core技术前沿周刊 | 第 19 期(2024年12.23-12.29)

前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。欢迎投稿、推荐或自荐优质文章、项目、学习资源等…

Notepad – – (文本编辑器) v3.0.0 官方版

这是一个使用C++编写的文本编辑器Notepad- -,可以支持Win/Linux/Mac平台。 我们的目标是要替换Notepad++,重点在国产Uos系统、Mac 系统上发展。 一个支持windows/linux/mac的文本编辑器,目标是要替换notepad++,来自中国。 对比Notepad++而言,我们的优势是可以跨平台,支持l…

【Node.js编程】实现GETPOST请求

创建基本的服务器 const express = require(express); const indexRouter = require(./router); // 引入路由 const app = express(); const port = 3000; // 挂载路由 app.use(/api, indexRouter); app.listen(port, () => {console.log(`Server is running on http://loca…

openEuler RISC-V上磁盘I/O性能测试的IOZone实例

IOZone 是一个广泛使用的文件系统性能基准测试工具,旨在评估磁盘 I/O 性能。它能够测试各种类型的文件操作,包括顺序读写、随机读写、重载(re-write)、读取已写入的数据等。通过这些测试,IOZone 可以帮助用户了解不同文件系统和存储设备的性能特点,从而为选择合适的硬件和…

Android 编译刷机刷入镜像img到手机

前言全局说明一、说明 1.1 环境准备: 下载和手机设备相同的驱动: https://developers.google.com/android/drivers?hl=zh-cn二、安装驱动 下载对应设备驱动后,是 sh 后缀文件,运行,按照提示安装即可。 (如果是虚拟机则不用装驱动,手机则需要)三、编译 3.1 环境生效 sou…

菜鸟的第一步

项目 详细信息这个作业属于哪个课程 走此小道这个作业要求在哪里 点击这里这个作业的目标 对本次软工进行总结与回顾学号 102201506多年以后,面对镜子中的地中海,我会想起上软工课程时的那些遥远的作业 📝一、学期回顾 1.1 回顾你对于软件工程课程的想象进入第一堂课之前,…

Navicat Premium Lite(数据库管理) v17.1.10 绿色版

Navicat Premium Lite 是 Navicat 的精简版,它包含了用户执行主要的基本数据库操作所需的核心功能。它允许你同时连接到各种数据库平台,包括 MySQL、PostgreSQL、SQL Server、Oracle、MariaDB,以及 Redis 和 MongoDB 等NoSQL 数据库,所有这些操作都可以通过一个应用程序来完…