本篇博客会讲解力扣“203. 移除链表元素”的解题思路,这是题目链接。
老规矩,先来审题:
以下是一些示例:
以下是提示:
本题的思路还挺多的,不过都是链表的常规操作。
思路1
万能的尾插法。遍历链表,找到所有不是val的结点,尾插到新的链表中。
首先创建哨兵位的头结点:
struct ListNode* removeElements(struct ListNode* head, int val){
// 定义哨兵位的头结点
struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));
newHead->val = 0;
newHead->next = NULL;
}
接着遍历并且尾插。由于单链表尾插需要找尾,建议定义个指针记录尾结点。
struct ListNode* removeElements(struct ListNode* head, int val){
// 定义哨兵位的头结点
struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));
newHead->val = 0;
newHead->next = NULL;
// 遍历并且尾插
struct ListNode* tail = newHead;
struct ListNode* cur = head;
while (cur)
{
if (cur->val == val)
{
// 删除
}
else
{
// 尾插
}
}
}
常规的删除和尾插的逻辑:
struct ListNode* removeElements(struct ListNode* head, int val){
// 定义哨兵位的头结点
struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));
newHead->val = 0;
newHead->next = NULL;
// 遍历并且尾插
struct ListNode* tail = newHead;
struct ListNode* cur = head;
while (cur)
{
if (cur->val == val)
{
// 删除
struct ListNode* next = cur->next;
free(cur);
cur = next;
}
else
{
// 尾插
tail->next = cur;
tail = cur;
cur = cur->next;
}
}
}
最后把尾部置空,释放哨兵位,再返回头结点。
struct ListNode* removeElements(struct ListNode* head, int val){
// 定义哨兵位的头结点
struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));
newHead->val = 0;
newHead->next = NULL;
// 遍历并且尾插
struct ListNode* tail = newHead;
struct ListNode* cur = head;
while (cur)
{
if (cur->val == val)
{
// 删除
struct ListNode* next = cur->next;
free(cur);
cur = next;
}
else
{
// 尾插
tail->next = cur;
tail = cur;
cur = cur->next;
}
}
// 尾部置空
tail->next = NULL;
// 释放哨兵位
struct ListNode* del = newHead;
newHead = newHead->next;
free(del);
del = NULL;
return newHead;
}
这样就过了。以上的思路是非常常见的通解,很多链表题目都会用到类似的思路,大家一定要熟练掌握。
思路2
这道题既然要删除结点,一个一个删就行了。遍历链表并且删除即可。
先定义哨兵位的头结点。由于要直接在链表中删除结点,所以在原链表前面定义即可。
struct ListNode* removeElements(struct ListNode* head, int val){
// 定义哨兵位的头结点
struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));
newHead->val = 0;
newHead->next = head;
}
接下来遍历链表,并且删除。
struct ListNode* removeElements(struct ListNode* head, int val){
// 定义哨兵位的头结点
struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));
newHead->val = 0;
newHead->next = head;
// 遍历并且删除
struct ListNode* cur = head;
while (cur)
{
if (cur->val == val)
{
// 删除
}
else
{
}
}
}
删除时应该注意什么呢?我们要让删除的结点的前一个指向删除的结点的后一个,所以需要记录前一个结点。
struct ListNode* removeElements(struct ListNode* head, int val){
// 定义哨兵位的头结点
struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));
newHead->val = 0;
newHead->next = head;
// 遍历并且删除
struct ListNode* cur = head; // 负责遍历
struct ListNode* prev = newHead; // 记录前一个结点
while (cur)
{
if (cur->val == val)
{
// 删除
prev->next = cur->next;
free(cur);
cur = prev->next;
}
else
{
prev = cur;
cur = cur->next;
}
}
}
最后释放哨兵位即可。
struct ListNode* removeElements(struct ListNode* head, int val){
// 定义哨兵位的头结点
struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));
newHead->val = 0;
newHead->next = head;
// 遍历并且删除
struct ListNode* cur = head; // 负责遍历
struct ListNode* prev = newHead; // 记录前一个结点
while (cur)
{
if (cur->val == val)
{
// 删除
prev->next = cur->next;
free(cur);
cur = prev->next;
}
else
{
prev = cur;
cur = cur->next;
}
}
// 释放哨兵位
struct ListNode* del = newHead;
newHead = newHead->next;
free(del);
del = NULL;
return newHead;
}
轻松通过。
思路3
这道题还可以使用递归来实现。
把问题转化一下:
- 递归调用自身,对除了头结点之外的结点进行操作。
- 操作头结点。
开始干代码:先处理特殊情况,因为比较好想。如果链表已经是空了,就先返回。
struct ListNode* removeElements(struct ListNode* head, int val){
// 链表已空
if (head == NULL)
return NULL;
}
接着操作除了头结点之外的结点,并且链接到头结点后面:
struct ListNode* removeElements(struct ListNode* head, int val){
// 链表已空
if (head == NULL)
return NULL;
// 操作除了头结点之外的结点
head->next = removeElements(head->next, val);
}
然后处理头结点。如果头结点是要删除的,就把头结点更新成head->next。
struct ListNode* removeElements(struct ListNode* head, int val){
// 链表已空
if (head == NULL)
return NULL;
// 操作除了头结点之外的结点
head->next = removeElements(head->next, val);
// 处理头结点
if (head->val == val)
{
// 删除头结点
struct ListNode* del = head;
head = head->next;
free(del);
del = NULL;
}
return head;
}
这就搞定了。递归的空间消耗比较大是很正常的。
总结
- 最通用的思路是尾插法,特殊的思路是直接删除。不管哪种思路,都建议定义哨兵位的头结点。
- 递归的思路也很常见。
感谢大家的阅读!