LeetCode - 83 - 删除链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1:

输入: 1->1->2
输出: 1->2
示例 2:

输入: 1->1->2->3->3
输出: 1->2->3

链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/

方法一:用一个工人指针遍历链表,比较前后两个元素是否相等

正常的逻辑输出其实就是这个思路,之前在LeetCode - 206 - 反转链表里谈过关于如何利用一前一后的的指针和头指针做题,而在本题中这两样其实都淡化了。
首先我用一个"工人指针"p (这个概念我在之后的博客中会详细谈到) “附魔"进入链表”。我也不知道我为什么会想到这么中二的两个名字来命名我的方法…附魔其实就是派一个指针去替我遍历完整个链表。

ListNode *p = head; 

其次我为了保证我前后的val值可以进行比较,我就要保证p->val 和 p->next->val都要存在。故我们可以得出让我的这个工人指针持续跑动的要求为 p != nullptr && p->next != nullptr。这样我才可以保证p->next->val一定存在

while ( p && p->next )

最后非常容易了,我们在循环中进行比较,如果p->val == p->next->val 则进行删除操作,不然就让p持续移动

if ( p->val == p->next->val ) 
	p->next = p->next->next;
else
	p = p->next;

于是完整的代码便自然而然的形成了:

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if ( !head || !head->next )
            return head;
        
        ListNode *p = head;
        while ( p && p->next ) {
            if ( p->val == p->next->val ) 
                p->next = p->next->next;
            else
                p = p->next;
        }
        
        return head;
    }
};

当然,使用一前一后的pre和cur也可以解这道题,你不妨一试。那样的模板大多数时候都是没有错误的!
另外还要注意一点,这里我的删除操作p->next = p->next->next删除操作没有对被删除结点进行free操作,C++这种没有垃圾回收机制的语言这里还是需要稍微注意一下的,但我第一次写的时候也忘了也没报错,所以这里就先抛开不管了8…

这是肯定会有小萌新有疑问,哎,你这个人真是多此一举,非搞个什么工人指针出来装大牌,直接使用head中进行比较不可以嘛?

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if ( !head || !head->next )
            return head;
        
        while ( head && head->next ) {
            if ( head->val == head->next->val ) 
                head->next = head->next->next;
            else
                head = head->next;
        }
        
        return head;
    }
};

结果控制台验证一看,惊呼,哎,前面重复的元素怎么一个都没保留下来???在这里插入图片描述
在这里插入图片描述
我们不妨来看看下面这两幅图
在这里插入图片描述
在这里插入图片描述
结合上面两张图我们不难发现,head是链表首个元素,故每一次删除操作都是从头开始进行的,而当有head->val != head->next->val的情况是,head变成
head->next,将链表进行了截断,把链表头移动到了后面,不再是原来的那个元素!最终返回的是截断后的链表,所以有元素丢失也不为怪。这个现象我称之为"头部丢失"。而当我使用一个工人指针,这个工人完整展现了这个链表到底是个什么样子,操作不会影响链表本身的结构,所以使用工人指针是一个非常安全的做法。

最后再讲一点,我之前所推荐大家用一个头结点在这道题有个不太行的地方。

ListNode *dummyHead = new ListNode(-1);
dummyHead->next = head;
ListNode *p = dummyHead;

while ( p && p->next ) {
	if ( p->val == p->next->val ) {
		// 当p = dummyHead,p->next = head时,也许就会出现dummyHead->val == head-val的情况,会造成不必要的删除
		// ...
	} 
}

小声说一句:我在当初这段代码报错的时候机智的把头结点值赋成了-65535(/ emoji funny)

方法二:递归

这个方法本身我没什么发言权,递归本来就不太熟,参考了一个dalao的解法,自己加了点自己的理解:
在这里插入图片描述

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
    	// Base Situation
        if ( head == null || head.next == null )
            return head;

        head.next = deleteDuplicates(head.next); // 已处理好的链表部分就是head.next
        if ( head.val == head.next.val )
            head = head.next; // 如果前后元素相等,就吞掉,递归只需要关注本级递归需要做什么
        
        return head;
    }
}

写递归除了理解模板完还要有点灵性,我愚钝,也没有太好的解释,我在刷链表的递归题中,经常就把整个链表看成某个结点,某个结点.next以及待处理的链表结点三部分,还挺管用的。

总结一下

1.工人指针的使用,有效避免了对链表本身结构的破坏。
2.遇事不决用递归…

本周LeetCode刷题就和大家聊到这里!你的关注就是我莫大的动力!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值