剑指 18(2) 删除链表中的重复节点
原题目
在一个排序的链表中,删除重复的节点
示例1:
原链表:1-> 2-> 3-> 3-> 4-> 4-> 5
删除后:1-> 2 -> 5
考查知识点
嵌套指针(指针的指针)
知识点记录
设计一个函数,首先要确定两点:传入参数和返回值。这是我们设计函数的最重要的两点,即我有什么样的信息,要去解决什么问题,最后才是怎样利用手里的信息去解决问题。
题目没有给传入参数,我们自己设计,这里就要结合题目经验,链表问题一般都是根据头结点来进行后续操作。
这里要注意的就是,如果题目不是直接返回头节点,那么我们就要将传入的形参头结点设置为嵌套指针(即指针的指针),比如ListNode **head
,这是因为我们可能在函数操作中改变头结点,比如删除原来的头结点或者设置新的头结点。
举一个形象的例子,如果是查询某链表中是否存在值为val
的节点,查询函数可以声明如下:
bool isExist(ListNode *head, int val);
isExist
函数传入的头结点就是单一的指针,head
这个形参其实是传值调用,实参头结点指针传值赋给形参,在isExist
函数结束后,形参head
销毁,对实参头结点指针没有影响。
但是如果是改变头结点的函数,代码如下:
void changeHead(ListNode **head);
void changeHead(ListNode *&head);
changeHead
必须为指针的指针,这是因为此时的形参head
要传址调用,在changeHead
内部能够真正改变形参背后的实参头结点指针。
比如如下的交换swap
函数:代码源自学习链表时遇到的问题:为什么要把一个头结点声明成结点为指针的指针呢?
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
void swap1(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void main()
{
int m = 1, n = 2;
swap(m, n);
cout << m << endl; //1
cout << n << endl; //2 没有变化
swap1(&m, &n);
cout << m << endl; //2
cout << n << endl; //1 有变化
}
解法
主要思想就是如果当前节点Node
与下一节点nextNode
的值相同,删除当前节点、释放内存,这里需要注意对头节点的操作。如果头结点时当前节点并且与下一节点重复,应该更改头结点。
还有一点是Node
、head
、toBeDelete
、preNode
、nextNode
这几个指针的指向顺序的更改。
class Solution{
public:
void DeleteDuplication(ListNode **head)
{
if (head == nullptr || *head == nullptr) return;
ListNode *preNode = nullptr; //当前节点的前一个节点
ListNode *Node = *head; //当前节点
while (Node != nullptr)
{
ListNode *nextNode = Node->next; //当前节点的下一节点
bool needDelete = false;
if (nextNode != nullptr && nextNode->val == Node->val)
needDelete = true;
if (!needDelete)
{
preNode = Node;
Node = Node->next;
}
else
{
int value = Node->val;
ListNode *toBeDelete = Node; //删除与下一节点重复的当前节点
while (toBeDelete != nullptr && toBeDelete->val == value)
{
nextNode = toBeDelete->next;
//删除 toBeDelete指向的节点
delete toBeDelete;
toBeDelete = nullptr;
toBeDelete = nextNode;
}
if (preNode == nullptr)
*head = nextNode;
else
preNode->next = nextNode;
Node = nextNode;
}
}
}
};