链表的递归思路与分析步骤:
我们以一道力扣上的经典题目为例子🌰:
删除排序链表中的重复元素
题目描述
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素只出现一次 。
返回同样按升序排列的结果链表。
图片:
输入:head = [1,1,2,3,3]
输出:[1,2,3]
解题思路:
递归法:
第一步:明白递归函数定义
递归最基本的是要明白递归函数的定义!
递归函数为 deleteDuplicates(head) ,它的含义是处理 head 作为开头的有序链表,使得值出现重复的节点仅保留一个。如果出现多个值相等的节点,我们保留最后一个节点。
第二步:找出递归的终止条件
终止条件就是能想到的基本的、不用继续递归处理的case。
🙈如果 head 为空,那么肯定没有值出现重复的节点,直接返回 head;
🙈如果 head.next 为空,那么说明链表中只有一个节点,也没有值出现重复的节点,也直接返回 head。
一般来说链表递归的终止条件都为head == nullptr || head->next==nullptr
第三步:分析递归的调用条件
什么时候需要递归呢?我们想一下这两种情况:
💕如果 head.val != head.next.val ,说明头节点的值不等于下一个节点的值,所以当前的 head 节点必须保留。
但是 head.next 节点要不要保留呢?我们还不知道,需要对 head.next 进行递归,即对 head.next 作为头节点的链表做处理,使值相等的节点仅保留一个。
然后我们看到self.deleteDuplicates(head) 函数就是做这个事情的!
所以 head.next = self.deleteDuplicates(head.next),这就是递归调用的由来。
💕如果 head.val == head.next.val ,说明头节点的值 等于 下一个节点的值,所以当前的 head 节点必须删除,删除到哪个节点为止呢?
按照函数定义,我们保留值相等的各个节点中最后一个节点,所以 head 到与 head.val 相等的最后一个节点之间的节点也都需要删除;
需要用 move 指针一直向后遍历寻找到最后一个与 head.val 相等的节点。
此时 move 之前的节点都不保留了,因此返回 deleteDuplicates(move);
第四步:找出返回条件
题目让我们返回删除了值重复的节点后剩余的链表,结合上面两种递归调用的情况。
🎃如果 head.val != head.next.val ,头结点需要保留,因此返回的是 head;
🎃如果 head.val == head.next.val ,头结点需要删除,需要返回的是 deleteDuplicates(move)。
如图:
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == nullptr || head->next==nullptr)
return head; //递归结束条件
if(head->val != head->next->val)
head->next = deleteDuplicates(head->next);//如果不相等,则头节点的next指针指向头节点的下一节点为头节点的无重复上升链表“递”的过程
else
{
ListNode *move = head->next;
while((move->next) && (head->val == move->next->val))
move = move->next;
return deleteDuplicates(move);//如果相等,则头节点不保存,头结点需要删除,需要返回的是 deleteDuplicates(move)。
}
return head;
}
};
时间复杂度:O(n) 空间复杂度:O(n)
补充:迭代法
由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。
具体地,我们从指针 cur 指向链表的头节点,随后开始对链表进行遍历。如果当前cur与 cur.next对应的元素相同,那么我们就将 cur.next 从链表中移除;否则说明链表中已经不存在其它与 cur 对应的元素相同的节点,因此可以将cur 指向 cur.next。
当遍历完整个链表之后,我们返回链表的头节点即可。
细节
当我们遍历到链表的最后一个节点时,cur.next 为空节点,如果不加以判断,访问 cur.next 对应的元素会产生运行错误。因此我们只需要遍历到链表的最后一个节点,而不需要遍历完整个链表。
错误为: runtime error: member access within null pointer of type 'struct ListNode’尝试使用空指针做null->next
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == nullptr)
return head;//特殊条件判定
ListNode *cur = head;
while(cur->next!= nullptr)
/*这里cur—>next的循环判定条件之所以为非空
1.为了循环
2.保证cur->next->next有意义,如果cur->next为空的话会报错:
runtime error: member access within null pointer of type 'struct ListNode'*/
{
if(cur->val == cur->next->val)
cur->next = cur->next->next;//如果相等就找到下一个不相等的节点为止
else
cur = cur->next;
}
return head;
}
};
时间复杂度:O(n) 空间复杂度:O(1)