news 2026/6/15 11:45:43

在链表中设立虚拟头结点

作者头像

张小明

前端开发工程师

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/15 7:30:27

【time-rs】Duration 结构体详解

这是一个 Rust 时间库中的 Duration 结构体实现&#xff0c;提供高精度的时间跨度表示。 1. 主要特性 纳秒级精度&#xff1a;由整秒和纳秒部分组成支持负值&#xff1a;与标准库的 std::time::Duration 不同&#xff0c;支持负时间间隔安全边界检查&#xff1a;使用 RangedI32…

作者头像 李华
网站建设 2026/6/16 1:20:20

10398_基于SSM的教学评价管理系统

1、项目包含项目源码、项目文档、数据库脚本、软件工具等资料&#xff1b;带你从零开始部署运行本套系统。2、项目介绍教学评价系统是以Java平台作为开发环境&#xff0c;采用MySQL数据库作为后台&#xff0c;使用Eclipse作为开发工具进行设计。本系统主要实现了教学评价模块、…

作者头像 李华
网站建设 2026/6/15 11:18:04

Go语言变量

Go变量声明的核心机制 静态类型语言要求变量在使用前必须声明&#xff0c;明确内存边界。Go作为静态语言&#xff0c;通过变量声明实现这一机制&#xff1a; 变量绑定特定内存区域&#xff0c;类型信息确定操作边界声明形式为&#xff1a;var 变量名 类型 值未显式初始化时自动…

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

【高可用系统架构】

系统高可用实现手段 冗余与无单点设计 部署关键节点时避免单点故障&#xff0c;例如负载均衡采用双节点Keepalived方案&#xff08;如Nginx/HAProxy/LVS&#xff09;&#xff0c;通过虚拟IP实现故障自动切换。网络通信配置多线路&#xff08;如移动电信双线&#xff09;&#x…

作者头像 李华
网站建设 2026/6/15 6:41:20

高频软件测试基础面试题

在软件测试的面试过程中&#xff0c;面试官会问些基础的软件测试知识&#xff0c;下面为大家整理了一些高频软件测试面试必备的基础题&#xff0c;拿走不谢~ 一、什么是软件测试 为了发现程序中的错误而执行程序的过程。 二、软件测试的原则 1、完全测试程序是不可能的 2、…

作者头像 李华
网站建设 2026/6/15 21:41:02

如何准确判断json文件并且拿到我想要的信息

写在前面&#xff0c;自从发现拿到json解析后的文件中有我们想要的信息后&#xff0c;我稍微有点迷上这种方法&#xff0c;但是拿到内容后要怎么拿到想要的信息呢&#xff0c;字典列表相互嵌套&#xff0c;我头都晕了方法&#xff1a;首先就是把json解析后的文本保存成.json的形…

作者头像 李华