news 2026/4/18 7:12:16

【LeetCode 手撕算法】(链表)160相交链表、206反转链表、234回文链表、141环形链表、142环形链表2、2两数相加、19删倒第N 结点、24交换结点、138随机链表复制、148排序链表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【LeetCode 手撕算法】(链表)160相交链表、206反转链表、234回文链表、141环形链表、142环形链表2、2两数相加、19删倒第N 结点、24交换结点、138随机链表复制、148排序链表

找中点用快慢指针,快的一次性找俩,慢的找一个

ListNode slow = head; ListNode fast = head; while(fast != null && fast.next != null){ slow = slow.next; fast = fast.next.next; }

遍历链表

ListNode cur = head; while(cur != null){ cur = cur.next; }

插入节点

// 在 node 后插入 newNode ListNode newNode = new ListNode(5); newNode.next = node.next; node.next = newNode;

反转链表

class Solution { public ListNode reverseList(ListNode head) { ListNode prev = null; ListNode cur = head; while(cur != null){ ListNode next = cur.next; // 保存下一节点 cur.next = prev; // 反转 prev = cur; // 前进 cur = next; } return prev; } }

删除倒数第n个

class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummy = new ListNode(0); dummy.next = head; ListNode fast = dummy; ListNode slow = dummy; // fast 先走 n 步 for(int i = 0; i < n; i++){ fast = fast.next; } // 一起走 while(fast.next != null){ fast = fast.next; slow = slow.next; } // 删除 slow.next = slow.next.next; return dummy.next; } }

160-相交链表

给你两个单链表的头节点headAheadB,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回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) { //先判空 if(headA==null||headB==null){return null;} //先找到长度 int numA=0,numB=0; ListNode tempA=headA;ListNode tempB=headB; while(tempA!=null){numA++;tempA=tempA.next;} while(tempB!=null){numB++;tempB=tempB.next;} //跳过长度,对齐 if(numA>=numB){ for(int i=0;i<numA-numB;i++){ headA=headA.next; } } if(numA<numB){ for(int i=0;i<numB-numA;i++){ headB=headB.next; } } //开始遍历,相等结束 while(headA!=headB){ headA=headA.next; headB=headB.next; } return headA; } }

细节注意:遍历链表取长度时,不能直接用原链表,会改变链表结构,选用新建temp来代替。


206-反转链表

思路:一个一个变,指向前面节点。但是要存好后一个,多多新建链表存不同的功能。新建翻转后的,新建平替操作的,新建下一个节点的。

/** * 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) { ListNode pre=null; ListNode tem=head; while(tem!=null){ ListNode next=tem.next;//存下一个 tem.next=pre; //翻转链表 pre=tem; //pre更新 tem=next;//tem更新 } return pre; } }

234-回文链表

给你一个单链表的头节点head,请你判断该链表是否为回文链表。如果是,返回true;否则,返回false

思路:因为是回文单链表,先找中点,前一半翻转,然后对比两段是否相同(长度相等不需像160题一样对齐砍掉)。注意区分奇偶数

/** * 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) { ListNode fast=head; ListNode slow=head; ListNode per=null; //找中点翻转前一半 while(fast!=null&&fast.next!=null){ fast=fast.next.next; ListNode next=slow.next; slow.next=per; per=slow; slow=next; } //奇数跳过中点,奇数时fast还剩一个 if(fast!=null){ slow=slow.next; } //比较两端,翻转后存入per,比较per和slow while(per!=null&&slow!=null){ if(per.val!=slow.val){return false;} per=per.next; slow=slow.next; } return true; } }

注意细节:

1、找中点的条件是 while (fast!=null &&fast.next != null )

2、比较两节点是否相等,用per.val 和slow.val

3、找中点奇数的话,最后多一个


141-环形链表

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

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

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

思路:新建哈希集合存节点,看是否已存在。set . add(xxx)为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) { Set <ListNode> visited=new HashSet<ListNode>(); while(head!=null){ if(!visited.add(head)){ return true; } head=head.next; } return false; } }

注意细节:1、新建集合 HashSet写法

2、set . add(xx)的逻辑

3、 if ( ! visited.add(head)) 会执行add,然后在判断是否存在


142-环形链表2

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

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

不允许修改 链表。

思路:和上一题一样,因为要返回下标,使用把head替换到pos。返回pos即返回此时的下标,返回 pos.val 即返回此时的值。使用pos原因(使用后 head不变,后期返回要用到head,因为head遍历后变成了null)

/** * 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) { Set <ListNode> visited=new HashSet<ListNode>(); ListNode pos= head; while(pos!=null){ if(!visited.add(pos)){ return pos; } pos=pos.next; } return null; } }

21-合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路:递归,一层一层两串进行比较并返回结果,从底层开始拼接结果。递归比正常while迭代的好处,不需要用新链表来接收,从底层开始自动拼接每一层返回结果。

判空则返回另一个有的,长度不一样时更适用。mergeTwoLists(a,b) 里面为去除开头的剩余部分。

/** * 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 mergeTwoLists(ListNode list1, ListNode list2) { if(list1==null){return list2;} else if(list2==null){return list1;} else if(list1.val<list2.val){ list1.next=mergeTwoLists(list1.next,list2); return list1; }else{ list2.next=mergeTwoLists(list1,list2.next); return list2; } } }

2-两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

思路:两数相加,按照顺序依次,满10进1,加到下一组。将某数字a存入链表,必须先链表。next=new ListNode(a)才行。 最后一位如果有进位,则变成新节点。

/** * 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 addTwoNumbers(ListNode l1, ListNode l2) { int tem=0; ListNode list=new ListNode(); ListNode head=list; while(l1!=null||l2!=null){ int x=(l1!=null) ?l1.val:0; int y=(l2!=null) ?l2.val:0; int sum=x+y+tem; tem=sum/10; int det=sum%10; head.next=new ListNode(det); head=head.next; if(l1!=null){l1=l1.next;} if(l2!=null){l2=l2.next;} if (tem > 0) { // 如果最后还有进位 head.next = new ListNode(tem); // 加入一个新的节点保存进位 } } return list.next; } }

注意细节如果新建链表,必须用head来接一下,最后返回的话,用链表.next 如果直接链表则开头为空


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

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

思路:快慢指针,快的先走n,慢的继续慢慢走。删除节点用a.next=a.next.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 removeNthFromEnd(ListNode head, int n) { ListNode list=new ListNode(); //新建返回链表 list.next=head;//与head对齐 ListNode fast=list; ListNode slow=list; for(int i=0;i<=n;i++){ fast=fast.next; } while(fast!=null){ fast=fast.next; slow=slow.next; } slow.next=slow.next.next; return list.next; } }

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

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

思路:新建dummy存新链表,使用prev来搬运,每次判空后两个,交换后两个。prev始终更新到已完成链表的尾节点,然后更新prev。

class Solution { public ListNode swapPairs(ListNode head) { // 创建 dummy 节点,虚拟头结点 ListNode dummy = new ListNode(0); dummy.next = head; // prev 指向当前要交换的前一个节点,搬运工 ListNode prev = dummy; // 只要后面还有两个节点,就继续交换 while(prev.next != null && prev.next.next != null){ ListNode a = prev.next; // 第一个节点 ListNode b = prev.next.next; // 第二个节点 a.next = b.next;// 开始交换 b.next = a; prev.next = b;// prev 指向 b,接上已处理的链表 prev = a;//更新prev } // 返回新头节点 return dummy.next; } }

138-随机链表的复制

给你一个长度为n的链表,每个节点包含一个额外增加的随机指针random,该指针可以指向链表中的任何节点或空节点。

构造这个链表的深拷贝。 深拷贝应该正好由n个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的next指针和random指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有XY两个节点,其中X.random --> Y。那么在复制链表中对应的两个节点xy,同样有x.random --> y

返回复制链表的头节点。

用一个由n个节点组成的链表来表示输入/输出中的链表。每个节点用一个[val, random_index]表示:

  • val:一个表示Node.val的整数。
  • random_index:随机指针指向的节点索引(范围从0n-1);如果不指向任何节点,则为null

你的代码 只 接受原链表的头节点head作为传入参数。

思路:每一个原节点后复制新节点,new一下。重新把随机指向关系放在新节点里。取出新头结点,拆分新节点。 每次头结点都需要重启。 注意判断条件是否null

/* // Definition for a Node. class Node { int val; Node next; Node random; public Node(int val) { this.val = val; this.next = null; this.random = null; } } */ class Solution { public Node copyRandomList(Node head) { if(head == null) return null; Node cur=head; //复制同样的一份在链表里 while(cur!=null){ Node newCur=new Node(cur.val); newCur.next=cur.next; cur.next=newCur; cur=newCur.next; } //重建链接关系 cur=head; //重启头结点 while(cur!=null){ if(cur.random!=null){ cur.next.random=cur.random.next; } cur=cur.next.next; } //拆分链表 Node newHead=head.next; Node newNode=newHead; cur=head;//重启头结点 while(cur!=null){ cur.next=cur.next.next;//恢复原节点 if(newNode.next!=null){ //只取新节点,新头为原头的下一位 newNode.next=newNode.next.next; } //新表剔除原节点 newNode=newNode.next; //新表走下一个 cur=cur.next;//走下一个 } return newHead; } }

给你链表的头结点head,请将其按 升序 排列并返回 排序后的链表 。

思路:排序且限制复杂度,链表归并排序。使用递归持续截一半,最小单元比较大小,拼接往上返回。递归终止条件---快慢指针找中点切割---递归执行---调用排序函数add()---add即按序合并两链表。

注意细节:递归判空条件必写。

找中点中归并排序的 fast起始值要多一个 因为目的是切割,不是找中间值。

切割设mid做右边链表的头结点。

结尾要把剩下的x或y存进去。

结果返回dummy.next dummy为虚拟头结点

/** * 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 sortList(ListNode head) { if(head==null||head.next==null){ //判空,递归的终止条件 return head; } //找中点 ListNode slow=head; ListNode fast=head.next;//起始多一个,利于切割,非找中间值 while(fast!=null&&fast.next!=null){ //fast去探路 slow=slow.next; fast=fast.next.next; } //切割 ListNode mid=slow.next;//做右边链表的表头 slow.next=null; //递归执行 ListNode left=sortList(head); ListNode right=sortList(mid); return add(left,right); } //拼接 public ListNode add(ListNode x,ListNode y){ ListNode dummy=new ListNode(); //存新结果 ListNode cur=dummy; //搬运工 while(x!=null&&y!=null){ if(x.val<y.val){cur.next=x; x=x.next;} //不能直接比,需要.val else{cur.next=y; y=y.next;} cur=cur.next; } if(x!=null){cur.next=x;} if(y!=null){cur.next=y;} return dummy.next; //dummy是头结点前一个 } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 8:00:40

手机高清一键投屏电脑 支持多设备群控

链接&#xff1a;https://pan.quark.cn/s/89ff3cfe88ae支持应用管理、屏幕录制、截图编辑美化&#xff1b; 应用管理功能支持软件列表查看和搜索&#xff0c;可以在电脑上方便的安装管理手机 App 。 支持多设备投屏&#xff0c;免费&#xff0c;无广告&#xff0c;支持win和mac…

作者头像 李华
网站建设 2026/4/18 8:03:16

Cosmos-Reason1-7B保姆级教程:GPU显存优化部署与物理常识推理实操

Cosmos-Reason1-7B保姆级教程&#xff1a;GPU显存优化部署与物理常识推理实操 1. 模型简介与核心能力 Cosmos-Reason1-7B是由NVIDIA开发的多模态物理推理视觉语言模型(VLM)&#xff0c;具备7B参数规模。作为Cosmos世界基础模型平台的核心组件&#xff0c;它专为物理理解与思维…

作者头像 李华
网站建设 2026/4/18 8:01:20

公式后面的编号右对齐做法、Visio的一些操作(写论文排版)

目录一、操作11. 第一步2. 第二步3. 第三步二、操作2一、操作1 1. 第一步 先将公式居中&#xff0c;箭头放到公式的第一个字母前面&#xff0c;点击上方工具栏中的 制表位 这里不居中也行&#xff0c;只是很多人都习惯先居中 也可以顶格直接打入公式&#xff0c;然后进行操作 …

作者头像 李华
网站建设 2026/4/15 15:22:28

vLLM-v0.17.1异步流式响应客户端开发:打造丝滑的聊天体验

vLLM-v0.17.1异步流式响应客户端开发&#xff1a;打造丝滑的聊天体验 1. 流式响应的革命性体验 想象一下这样的场景&#xff1a;当你向AI提问时&#xff0c;答案不是等待几秒后突然全部出现&#xff0c;而是像真人对话一样逐字逐句流畅展现。这正是vLLM-v0.17.1的流式输出特性…

作者头像 李华
网站建设 2026/4/18 7:53:04

使用Nunchaku-flux-1-dev进行数据库课程设计可视化

使用Nunchaku-flux-1-dev进行数据库课程设计可视化 在数据库课程的教学过程中&#xff0c;学生经常面临一个共同的难题&#xff1a;如何将抽象的数据模型和复杂的查询逻辑转化为直观的可视化表达。传统的绘图工具需要手动创建每一个实体、关系和箭头&#xff0c;不仅耗时耗力&…

作者头像 李华