news 2026/6/14 0:04:23

在链表中设立虚拟头结点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在链表中设立虚拟头结点

在处理链表的删除操作时,一般先找到待删除结点的前驱,否则会断链。而对于没有虚拟头结点的链表,删除第一个结点不好处理,因为没有头结点(但不影响删除最后一个结点),此时就需要构造一个虚拟头结点,可以更方便的完成所有可能的删除操作。

题目1:删除链表中所有值为val的结点,如1->2->6->3->4->5->6->null,要求删除值为6的结点,返回1->2->3->4->5->null。

公用代码:

​ public class ListNode { public int val; public ListNode next; public ListNode(int x) { this.val = x; this.next = null; } public static ListNode createList(int[] nums) { if(null == nums || 0 == nums.length) return null; ListNode head = new ListNode(nums[0]); ListNode needle = head; for(int i = 1; i < nums.length;++i) { ListNode node = new ListNode(nums[i]); needle.next = node; needle = needle.next; needle.next = null; } return head; } }

我们先给出不用虚拟头结点完成链表删除操作的过程,看看具体繁琐在哪?

// 203 没有虚拟头结点的情况下需要下面的判断才能处理val等于头结点的情况 public ListNode removeElements(ListNode head, int val) { // 循环处理头结点(有多个与头结点值相同的结点,且删除的就是它们) while (head != null && head.val == val) { // head一直在变换 head = head.next; } // 在进行下面的while循环之前判断一下 if (head == null) return null; // 下面的逻辑是删除非头结点(删除头结点在上面已经处理了) ListNode pre = head; while (pre.next != null) { // 找到了要删除的结点 if (pre.next.val == val) pre.next = pre.next.next; else // 没有找到直接移动指针遍历下一个结点即可 pre = pre.next; } return head; }

上面的处理需要单独处理头结点,下面这段程序是通过添加了一个虚拟头结点来避免这种情况。

// 203 添加了虚拟头结点,更简单且便于理解 public ListNode removeElements1(ListNode head, int val) { // 创建一个虚拟头结点,这样不用单独处理头结点 ListNode dummyHead = new ListNode(-1); dummyHead.next = head; ListNode pre = dummyHead; // 从实际的头结点开始遍历,但是保证了实际的头结点具有自己的前驱 while (pre.next != null) { // 找到了要删除的结点 if (pre.next.val == val) pre.next = pre.next.next; else// 没找到了要删除的结点,直接遍历下一个结点 pre = pre.next; } // 一定注意dummyHead指向一只未变化,变化的是临时指针pre return dummyHead.next; }

题目2:给定一个有序链表,将其中有重复的元素全部删除

如1->2->3->3->4->4->5->null,返回1->2->5;再如1->1->1->2->3,返回2->3。

实现方式一:使用检查表的方式,效率较低。

import java.util.ArrayList; import java.util.List; public class Solution { public ListNode deleteDuplicates(ListNode head) { if (null == head) return null; ListNode pointer = head; List<Integer> tmpList = new ArrayList<Integer>(); int index = -1; // 记录想等元素的个数,避免漏删 int equalCnt = 1; while (pointer != null) { int value = pointer.val; // 临时列表中不存在,就添加进去,对应的索引值加1,只是添加重复元素的第一个值 if (!tmpList.contains(value)) { // 尝试删除所有值等于上一个元素值的元素,有则删除 if (equalCnt > 1) { tmpList.remove(index--); } // 添加新的元素 tmpList.add(value); index++; // 将新元素的equalCnt置为1 equalCnt = 1; } else {// 临时列表中存在,不添加只记录重复元素的个数,以便下面的删除操作 equalCnt++; // 最后一个元素与前面的元素重复的情况 if (null == pointer.next) tmpList.remove(index--); } // 指针后移,遍历下一个链表元素 pointer = pointer.next; } // 使用虚拟头结点从新创建一个链表,这种方法思路简单,辅助空间大 ListNode dummyHead = new ListNode(-1); ListNode cur = dummyHead; for (Integer tmp : tmpList) { cur.next = new ListNode((int) tmp); cur = cur.next; } return dummyHead.next; } public static void main(String[] args) { int[] nums = { 1, 1 }; ListNode link = ListNode.createList(nums); ListNode result = new Solution().deleteDuplicates(link); while (null != result) { System.out.print(result.val + " "); result = result.next; } } }

实现方式二:使用两个列表作为检查表,记录元素出现的次数,但是使用map不行,因为map存储数据的时候会根据hash值进行排序,导致乱序。例如{2,1},输出为1->2。

import java.util.ArrayList; import java.util.List; class Solution { public ListNode deleteDuplicates(ListNode head) { if (null == head) return head; // 定义两个list来充当检查表,valus表示元素的值,times表示对应值出现的次数 List<Integer> values = new ArrayList<Integer>(); List<Integer> times = new ArrayList<Integer>(); int time = 1; boolean first = true; while (head != null) { int value = head.val; // 第一个结点需要特殊处理 if (!values.contains(value)) { // 将上一个元素的出现次数添加到times中 if (!first) // 第一个元素出现的次数到与他值不同的元素出现时才添加 times.add(time); // 添加新元素到values中 values.add(value); // 重置time time = 1; } else // 元素重复时出现次数加1 time++; head = head.next; first = false; // 到了最后一个结点,需要特殊处理来记录最后一个元素出现的次数 if (head == null) times.add(time); } // 定义一个虚拟结点 ListNode dummyHead = new ListNode(-1); // 定义一个穿针引线的指针,让它去串连所有合法的结点,头结点只指向头结点,不需要移动, // 且一定将该指针与头结点建立联系,否则会断链 ListNode tmpNode = dummyHead; for (int i = 0; i < times.size(); ++i) { if (1 == times.get(i)) { tmpNode.next = new ListNode(values.get(i)); tmpNode = tmpNode.next; } } return dummyHead.next; } public static void main(String[] args) { int[] nums = { 1, 1, 1, 2 }; ListNode link = ListNode.createList(nums); ListNode result = new Solution().deleteDuplicates(link); while (null != result) { System.out.print(result.val + " "); result = result.next; } } }

实现方式三:使用双指针直接操作链表,效率更高。

public class LC82 { public ListNode deleteDuplicates(ListNode head) { ListNode dummyHead = new ListNode(-1); dummyHead.next = head; ListNode previous = dummyHead; // 从头结点开始遍历,因为要删除所有重复的结点,要考虑前驱previous下的两个位置的结点。 while (previous.next != null && previous.next.next != null) { if (previous.next.val == previous.next.next.val) { int dupValue = previous.next.val; while (previous.next != null && previous.next.val == dupValue) { previous.next = previous.next.next; } } else { previous = previous.next; } } return dummyHead.next; } public static void main(String[] args) { int[] nums = { 2, 1 }; ListNode link = ListNode.createList(nums); ListNode result = new LC82().deleteDuplicates(link); while (null != result) { System.out.print(result.val + " "); result = result.next; } } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 2:30:13

软件测试面试题集合

软件测试面试题,这是一份集锦&#xff0c;也是一份软件测试人员 学习的好工具书&#xff0c;非常实用。 01. 为什么要在一个团队中开展软件测试 工作&#xff1f; 因为没有经过测试的软件很难在发布之前知道该软件的质量&#xff0c;就好比 ISO 质量认证一样&#xff0c;测试同…

作者头像 李华
网站建设 2026/6/12 12:26:44

OpenVSCode Server终极性能调优与资源管理完整指南

OpenVSCode Server终极性能调优与资源管理完整指南 【免费下载链接】openvscode-server 项目地址: https://gitcode.com/gh_mirrors/op/openvscode-server OpenVSCode Server作为基于浏览器的代码编辑器服务器&#xff0c;其性能表现直接影响开发效率。本文将为您提供一…

作者头像 李华
网站建设 2026/6/12 0:30:25

【系统微服务化】

微服务化改造的关键步骤 圈定服务边界与数据表 确定微服务包含哪些数据表是改造的第一步。库存服务涉及15张表&#xff0c;包括自营库存表、商家虚拟库存表等。这些表与商品基本信息表关联较弱&#xff0c;便于独立拆分。业务架构师和数据架构师需深入分析业务场景和表关系&…

作者头像 李华
网站建设 2026/6/12 7:16:00

高可用架构(一)

高可用架构改造要点总结 针对小程序点餐平台的高并发场景&#xff08;10万QPS、500万日订单、99.99%可用性&#xff09;&#xff0c;以下是关键改造措施&#xff1a; 前端接入优化CDN加速静态资源 商品图片等静态数据通过多地CDN节点分发&#xff0c;减少服务端负载。Nginx集群…

作者头像 李华
网站建设 2026/6/13 18:28:52

终极指南:如何为泉盛UV-K5对讲机刷入开源固件实现专业功能

终极指南&#xff1a;如何为泉盛UV-K5对讲机刷入开源固件实现专业功能 【免费下载链接】uv-k5-firmware-custom This is a fork of Egzumer https://github.com/egzumer/uv-k5-firmware-custom 项目地址: https://gitcode.com/gh_mirrors/uvk/uv-k5-firmware-custom 想要…

作者头像 李华