链表去重是算法面试中的经典问题,LeetCode第83题要求我们删除排序链表中所有重复的元素,使每个元素只出现一次。这个问题看似简单,却涵盖了链表操作的核心知识点,是检验程序员基本功的试金石。
在实际开发中,处理有序数据去重的场景比比皆是。比如用户行为日志分析时,需要合并连续相同的操作记录;数据库查询结果优化时,可能需要对有序的主键列表进行去重处理。掌握链表去重的技巧,不仅能帮助我们通过技术面试,更能提升日常开发中处理链式数据的效率。
链表是由节点组成的数据结构,每个节点包含数据域和指针域。在单链表中,指针域存储下一个节点的地址。与数组不同,链表不需要连续的内存空间,插入删除操作的时间复杂度为O(1),但随机访问需要O(n)时间。
对于本题,给定的链表已经排序,这意味着所有重复元素必然相邻。这个特性大大简化了问题难度,我们只需要比较相邻节点即可发现重复。
最常见的解法是使用双指针技巧:
当fast指针发现新元素时,将slow.next指向fast,然后slow前进;遇到重复元素时,fast继续前进直到找到不同值。这种方法只需要一次遍历,时间复杂度O(n),空间复杂度O(1)。
注意:处理链表问题时,一定要先考虑头节点可能被删除的情况。建议使用哑节点(dummy node)作为链表的新头节点,可以统一处理逻辑。
python复制class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def deleteDuplicates(head: ListNode) -> ListNode:
if not head:
return head
current = head
while current.next:
if current.val == current.next.val:
current.next = current.next.next
else:
current = current.next
return head
代码解析:
python复制def deleteDuplicates(head: ListNode) -> ListNode:
dummy = ListNode(0, head)
current = dummy
while current.next and current.next.next:
if current.next.val == current.next.next.val:
current.next = current.next.next
else:
current = current.next
return dummy.next
优化点:
两种实现方式都是单次遍历链表,时间复杂度为O(n),其中n是链表长度。每个节点最多被访问两次(比较和指针移动),因此实际运行时间与链表长度成线性关系。
空间复杂度方面,只使用了常数级别的额外空间(几个指针变量),因此是O(1)。这是原地算法(in-place algorithm)的典型特征。
当链表数据量极大时(无法一次性装入内存):
java复制public ListNode deleteDuplicates(ListNode head) {
ListNode current = head;
while (current != null && current.next != null) {
if (current.val == current.next.val) {
current.next = current.next.next;
} else {
current = current.next;
}
}
return head;
}
Java特点:
cpp复制ListNode* deleteDuplicates(ListNode* head) {
ListNode* current = head;
while (current && current->next) {
if (current->val == current->next->val) {
ListNode* temp = current->next;
current->next = current->next->next;
delete temp; // 需要显式释放内存
} else {
current = current->next;
}
}
return head;
}
C++注意事项:
全面的测试应该包含以下情况:
单元测试示例(Python unittest):
python复制import unittest
class TestSolution(unittest.TestCase):
def test_empty(self):
self.assertIsNone(deleteDuplicates(None))
def test_no_duplicates(self):
head = ListNode(1, ListNode(2, ListNode(3)))
result = deleteDuplicates(head)
self.assertEqual([1,2,3], list_nodes(result))
def test_all_duplicates(self):
head = ListNode(1, ListNode(1, ListNode(1)))
result = deleteDuplicates(head)
self.assertEqual([1], list_nodes(result))
辅助函数list_nodes用于将链表转换为列表便于断言。
理解链表操作的最佳方式是可视化:
初始状态:
code复制1 -> 1 -> 2 -> 3 -> 3 -> null
^
current
发现重复:
code复制1 -> 1 -> 2 -> 3 -> 3 -> null
^ ^
| next
current
删除操作后:
code复制1 -> 2 -> 3 -> 3 -> null
^
current
移动指针:
code复制1 -> 2 -> 3 -> 3 -> null
^
current
使用动画或分步图解能显著提升理解效率,推荐工具:
掌握了基础解法后,可以思考以下进阶问题:
如果链表未排序,如何高效去重?
如何实现递归解法?
python复制def deleteDuplicates(head):
if not head or not head.next:
return head
head.next = deleteDuplicates(head.next)
return head.next if head.val == head.next.val else head
如何扩展到双向链表?
如何处理环形链表中的重复?
对于想深入链表算法的学习者,推荐以下资源: