【LeetCode】82. 删除排序排序链表中的重复元素 II
【LeetCode】82. 删除排序排序链表中的重复元素 II
题目
【LeetCode】82. 删除排序排序链表中的重复元素 II
给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
示例 1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
示例 2:
输入:head = [1,1,1,2,3]
输出:[2,3]
提示:
链表中节点数目在范围 [0, 300] 内
-100 <= Node.val <= 100
题目数据保证链表已经按升序 排列
解题思路
核心要点:
- 链表已排序,因此重复元素必然连续出现
- 需要删除所有重复的节点,而不仅仅是去重保留一个
- 可能需要删除头节点,因此使用虚拟头节点会更方便
有效的解法是一次遍历法:
- 创建虚拟头节点(dummy node),其next指向原链表头节点,便于处理头节点可能被删除的情况
- 使用prev指针指向当前确定不重复的节点,curr指针用于遍历链表
- 当curr和curr.next的值相同时,说明出现重复,记录当前重复值,然后将curr移动到最后一个重复节点的下一个位置
- 如果没有出现重复,则将prev移动到curr位置,curr继续后移
- 遍历结束后,返回虚拟头节点的next
这种方法只需一次遍历,时间复杂度为O(n)。
代码实现
class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) { this.val = val; }ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}public class RemoveDuplicates {public ListNode deleteDuplicates(ListNode head) {// 创建虚拟头节点,处理头节点可能被删除的情况ListNode dummy = new ListNode(0);dummy.next = head;// prev指向当前确定不重复的节点ListNode prev = dummy;// curr用于遍历链表ListNode curr = head;while (curr != null) {// 标记是否有重复boolean hasDuplicate = false;// 找到当前重复元素的最后一个节点while (curr.next != null && curr.val == curr.next.val) {hasDuplicate = true;curr = curr.next;}if (hasDuplicate) {// 如果有重复,跳过所有重复节点prev.next = curr.next;} else {// 如果没有重复,prev移动到当前节点prev = curr;}// curr继续后移curr = curr.next;}return dummy.next;}// 测试示例public static void main(String[] args) {RemoveDuplicates solution = new RemoveDuplicates();// 示例1: 1->2->3->3->4->4->5ListNode head1 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(3, new ListNode(4, new ListNode(4, new ListNode(5)))))));ListNode result1 = solution.deleteDuplicates(head1);printList(result1); // 输出:1->2->5->null// 示例2: 1->1->1->2->3ListNode head2 = new ListNode(1, new ListNode(1, new ListNode(1, new ListNode(2, new ListNode(3)))));ListNode result2 = solution.deleteDuplicates(head2);printList(result2); // 输出:2->3->null}// 打印链表,格式为[val1, val2, ...]private static void printList(ListNode head) {StringBuilder sb = new StringBuilder();sb.append("[");ListNode curr = head;while (curr != null) {sb.append(curr.val);if (curr.next != null) {sb.append(",");}curr = curr.next;}sb.append("]");System.out.println(sb.toString());}
}
复杂度分析
- 时间复杂度:O(n),其中n是链表的长度。我们只需要遍历一次链表。
- 空间复杂度:O(1),只使用了常数个额外变量。
关键要点
- 虚拟头节点的使用:简化了头节点可能被删除的情况处理
- 双指针技巧:prev指针跟踪已确认不重复的节点,curr指针负责遍历和发现重复节点
- 一次遍历即可完成,效率高
- 处理重复节点的逻辑:当发现重复时,将prev的next直接指向最后一个重复节点的下一个位置,从而删除所有重复节点