内容简介:本文始发于个人公众号:
本文始发于个人公众号: TechFlow ,原创不易,求个关注
今天是 LeetCode专题 的第51篇文章,我们来看LeetCode第82题,删除有序链表中的重复元素II(Remove Duplicates from Sorted List II)。
这题官方给出的难度是 Medium ,点赞1636,反对107,通过率在36.3%左右。根据我们之前的分析,这题的难度适中,并且质量很高,好评如潮。实际上也的确如此,这题算法本身并不难,但是想要完整没有bug地实现并不容易,我们一起来看看。
题意
给定一个有序的 存在重复元素的链表 ,要求移除掉链表当中所有的重复元素。返回一个不包含重复元素的链表。
这里要注意的一点,这题让我们做的事情并不是去重,就是去除掉多余的元素,而是要去除掉所有重复的元素。比如2在链表当中出现了两次,属于重复元素,我们要做的并不是去掉一个2,仅保留一个,而是要 将所有的2都去除 ,因为2属于重复元素。
我们来看样例:
Input: 1->2->3->3->4->4->5
Output: 1->2->5
原链表当中的3和4都属于重复元素,所以被去除了。
Input: 1->1->1->2->3
Output: 2->3
解法
前面说了这题的质量很高,这题是属于典型的解法赤裸裸,但是很多人就是写不出来的题, 非常考察基本功 。适合用在校招面试当中,如果我有幸去面试校招生, 我可能会选这道题。不存在算法会不会的问题,写不出来一定是基本功不够扎实。
链表已经有序了,那么 相同的元素必然会排在一起 ,我们只需要将它们移除就可以了。但是说起来简单,要在链表当中实现并不容易。难点主要有两个,一个是链表增删节点的操作很多人不熟悉,尤其是像是C++这样的语言涉及指针,可能更不容易。另外一个难点就在题意当中,我们要做的不是去重,而是 要所有重复的元素全部删除 。
看起来似乎和去重没什么差别, 如果你真这么想,并且着手去实现,那么几乎可以肯定一定会遇到问题。
因为链表是单向的,假设你当前的指针是cur,当你发现cur这个指针的元素存在重复的时候,你需要连当前这个节点一起删除。我们都知道, 单向链表是不能走回头路的,而删除节点,必须要用到前一个节点的指针 。再加上判断元素重复需要用到的指针,会需要我们同时维护多个指针,增加代码的编码难度。
针对这个问题,我们有两种解决思路。第一种是我们不在原链表上处理,而是 创建一个新的链表进行返回 。所以我们要做的就不是删除元素,而是插入元素,只有发现当前元素不存在重复的时候才会插入链表。最后返回的也是一个全新的链表。
这当然是可以的,也是没有问题了,我第一次做这道题就是采取的这种措施。相比之下, 这种方法会容易一些,因为我们 不需要判断太多的指针和位置 ,我贴一下当时的代码:
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
// 如果元素少于2个则直接返回
if (head == nullptr || head->next == nullptr) return head;
int cur = head->val;
// 初始化
bool flag = false;
ListNode* ct = new ListNode(0);
ListNode* pnt = ct;
head = head->next;
// 遍历元素
while (head) {
int hd = head->val;
// 判断当前元素与之前的元素是否相等
if (hd != cur) {
// 之前的元素没有出现重复
if (!flag) {
// 把之前的元素存入链表
pnt->next = new ListNode(cur);
// 链表移动
pnt = pnt->next;
}else flag = false;
// 更新之前的元素
cur = hd;
}else flag = true;
head = head->next;
}
// 单独处理最后一个元素
if (!flag) pnt->next = new ListNode(cur);
return ct->next;
}
};
虽然题目当中没有对解法做出限制,也没有规定我们必须要在原链表上进行处理,但是这种创建新链表的方法终归有绕开问题的嫌疑。所以我们还有第二种解法,就是直面问题,我们维护多个指针,判断当前位置的下一个元素是否构成重复。如果重复,则删除掉重复的部分。
正如我们之前所说的那样,在单向链表当中很难删除当前元素,所以我们 判断下一个元素是否会构成重复 。如果重复的话,进行删除要可行许多。这样也有一个问题就是, 有可能链表的第一个元素就是重复的 ,我们没有办法找到第一个元素的上一个元素。针对这个问题,我们采用的方法是 人为给它创造一个元素放在首元素之前 。这样我们整个流程就可以串起来了,唯一的难点就是编码了。
我们仔细一些,写出代码还是可以的:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
# 创建一个新的元素放在链表头部,这样原本第一个元素就是头指针的下一个元素了
node = ListNode()
node.next = head
# pnt指向新创建的元素
pnt = node
while pnt.next is not None:
# cur指向当前位置的下一个位置
# 我们要做的即判断cur这个指针的元素是否重复
cur = pnt.next
if cur.next is None:
break
# ptr指向cur的next
ptr = cur.next
# 如果ptr和cur相等,那么出现重复,我们需要跳过所有相等的元素
if ptr is not None and ptr.val == cur.val:
while ptr is not None and ptr.val == cur.val:
ptr = ptr.next
pnt.next = ptr
# 否则说明不重复,移动pnt
else:
pnt = pnt.next
# 由于我们开始的时候人为添加了一个辅助元素
# 返回的时候要将它去除
return node.next
总结
这道题的算法很简单,我想大部分人都能想出解法来,但是要将解法实现并不太容易。这当中用到了很多小技巧,比如我们认为创建了一个新的头结点,比如我们将删除当前元素转化成了删除下一个元素等等。这些技巧虽然算不上什么,但是灵活使用,可以大大降低我们编码的复杂度,也正是因为这一点,这题的质量非常高,值得一做。
很多人非常讨厌涉及链表的问题,觉得链表很难操作,容易写错,但实际上 这是基本功的很重要的一部分 。很多公司喜欢考察候选人的基本功,提升这方面的能力对于我们应聘或者是工作非常有帮助。
今天的文章到这里就结束了,如果喜欢本文的话,请来一波 素质三连 ,给我一点支持吧( 关注、转发、点赞 )。
本文使用 mdnice 排版
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing Data-Intensive Applications
Martin Kleppmann / O'Reilly Media / 2017-4-2 / USD 44.99
Data is at the center of many challenges in system design today. Difficult issues need to be figured out, such as scalability, consistency, reliability, efficiency, and maintainability. In addition, w......一起来看看 《Designing Data-Intensive Applications》 这本书的介绍吧!