个人评价:链表类题目。。。难点在于指针和头尾处理,调试真难 😂

题目难度:🌟 🌟 (参考 LeeCode 简单🌟 、中等🌟 🌟 、困难🌟 🌟 🌟 )

另,通常算法题会考察分析时间复杂度和空间复杂度,可参考笔者之前的文章

「数据结构&算法」必知必会系列:3. 时间 & 空间复杂度(下)

链表可回炉:「数据结构&算法」必知必会系列:7. 常见数据结构——链表(下)

删除排序链表中的重复元素 II

题目描述

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 题目数据保证链表已经按升序排列

解题思路

采用遍历,思路如下:

  • 当前值与之后的值对比,不同 =》继续(已经排序,可确定唯一)
  • 当前值与之后的值对比,相同 =》继续循环判断与下下个节点值是否相同

注意加入 哨兵节点/哑结点 用于可能的头结点删除处理

存在内循环,可考虑使用递归,个人认为此题无需引入,会增加理解的复杂性

func deleteDuplicates(head *ListNode) *ListNode {
    if head == nil {
        return nil
    }

  	// 引入 哨兵节点/哑结点 减少头结点删除的难度
    dummy := &ListNode{0, head}

    cur := dummy
    for cur.Next != nil && cur.Next.Next != nil {
        if cur.Next.Val == cur.Next.Next.Val {
            x := cur.Next.Val
            for cur.Next != nil && cur.Next.Val == x { // 循环删除,直至不同
                cur.Next = cur.Next.Next
            }
        } else {
            cur = cur.Next
        }
    }

    return dummy.Next
}

引申

一开始看错了。。。没有看清楚是排序链表,故使用了 Hash 记录,并且只做了去重,类似 uniq,并没有删除重复的数字本身(还在想中等题,确定么?😂)

func deleteDuplicates(head *ListNode) *ListNode {
	if head == nil || head.Next == nil {
		return head
	}
	prev := head
	curr := head
	nMap := make(map[int]bool)
	for curr.Next != nil {
		next := curr.Next
		if _, ok := nMap[curr.Val]; ok { // 如果存在,删除节点
			*curr = *curr.Next // 删除链表节点(不可为尾)
			continue
		}
		nMap[curr.Val] = true
		prev = curr
		curr = next
	}
	if _, ok := nMap[curr.Val]; ok { // 尾结点删除,特殊处理
		prev.Next = nil
	}
	return head
}

总结

引入哨兵结点,在处理链表题还是很好用的。另外,题多看几遍 😂