好久没有刷题与更文了,今天来一场LeetCode上面简单与中等题目多种方法刷题。
83. 删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2输出: 1->2示例 2:
输入: 1->1->2->3->3输出: 1->2->3
2.思想
(1)使用快慢指针
特殊情况判断:链表为空或链表只有一个节点,直接返回。
设p=head,q=head->next,让不断去移动,直到q的val不等于p的val,那么将p连接上q即可。
循环特殊情况判断,当快指针指向为空,直接让p指向NULL,break掉函数,返回即可。
分析:时间复杂度为O(n),空间复杂度为O(1)。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head==NULL||head->next==NULL) return head;
ListNode *p=head,*q=p->next;
while(p) {
if(p->val!=q->val) {
p->next=q;
p=q;
}
q=q->next;
if(q==NULL){
p->next=NULL;
break;
}
}
return head;
}
};
(2)递归
递归终止条件:无节点或只有一个节点。
递归到最后,例如尾部节点为2 2,也就是当head->next指向末尾的2时候,此时需要判断head与head->next值是否相等,如果相等,直接让head指向尾部,依次覆盖所有重复节点。否则,直接让head指向尾部节点。
分析:时间复杂度为O(n),空间复杂度为O(1)。
ListNode* deleteDuplicates(ListNode* head) {
//链表是否为空或者是否到末尾
if (head == NULL || head->next == NULL)
return head;
ListNode *p = deleteDuplicates(head->next);
if (head->val == head->next->val)
head = p;
else
head->next = p;
return head;
}
2.题目
82. 删除排序链表中的重复元素 II
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1:
输入: 1->2->3->3->4->4->5输出: 1->2->5示例 2:
输入: 1->1->1->2->3输出: 2->3
3.思想
3.1方法一
这道题与上述题目很相似,于是采用上述题目思想完成。
上述思想中核心就是快慢指针,快指针q,满指针p,每次q指向的是新元素,也就是满足p->val!=q->val,就需要判断当前链表是否值不同连续。
值不同连续判断就是p->next==q,(两者距离只差1)。如果满足,说明当前p指向的元素无重复,那么直接让r(此指针为新返回链表的遍历指针)指针指向p指向的节点(注意这里是创建了一个p->val相同的节点),r指针再指向下一个节点,q指针处理是不作为循环的遍历指针,每次++。
到最后,q指针为空,分为两种情况:
(1)值不同不连续: 例如:[1,2,2] p指向了2,q指向了NULL,此时需要将r->next指针直接指向末尾的NULL*
(2)值不同连续: 例如:[1,2,2,5] p指向了5,q指向了NULL,此时需要连接p,即r->next=p
ListNode* deleteDuplicates(ListNode* head) {
if(head==NULL || head->next==NULL) return head;
ListNode* p=head,*q=p->next;
ListNode* HEAD=new ListNode(0),*r=HEAD;
while(q) {
cout<<p->val<<" "<<q->val<<endl;
if(p->val!=q->val) {
if(p->next==q) {
r->next=new ListNode(p->val);
r=r->next;
}
p=q;
}
cout<<r->val<<endl;
q=q->next;
}
if(p->next!=q)
r->next=NULL;
else
r->next=new ListNode(p->val);
r=HEAD->next;
delete HEAD;
return r;
}
分析:*时间复杂度:O(n),空间复杂度:O(n)。
那能否优化空间复杂度为O(1)呢?
我们继续优化。
3.2方法二
上述的空间复杂度耗费在每次都要去创建新的节点,那么我们不创建不就行了,只需要拓展一个指针,让该指针不断动态修改链表。
ListNode* deleteDuplicates(ListNode* head) {
// 无节点或者只有一个节点情况
if(head==NULL || head->next==NULL) return head;
// p跳跃指向不同节点,q遍历所有节点,r动态修改链表
ListNode* p=head,*q=p->next,*r=p;
// 针对 [1 1 2] 这种情况需要先确定返回链表头结点,那么这里创建一个头结点,让上述定义的r指针取动态的修改返回链表。
ListNode* HEAD=new ListNode(0); // 返回链表的头指针
while(q) {
/**
* 值不同
*/
if(p->val!=q->val) {
/**
* 值不同且连续
*/
if(p->next==q) {
/**
* 返回链表是否有开始节点
*/
if (HEAD->next == NULL) {
r = p;
HEAD->next = r;
} else {
r->next = p;
r = p;
}
}
// p指向新值
p=q;
}
q=q->next; //遍历链表
}
if(p->next!=q)
r->next=NULL;
else {
if(HEAD->next==NULL)
HEAD->next=p;
else
r->next=p;
}
r=HEAD->next;
delete HEAD;
return r;
}
到最后,q指针为空,分为两种情况:
(1)值不同不连续
例如:[1,2,2] p指向了2,q指向了NULL,此时需要将r->next指针直接指向末尾的NULL
(2)值不同连续
值不同连续,且返回链表的没有开始节点,也就是HEAD->next=NULL
例如:[1,1,2] p指向了2,q指向了NULL,此时需要连接p,但是Head->next为空,直接让HEAD->next指向p即可
值不同连续,且回链表的有开始节。
例如:[1,2,2,5] p指向了5,q指向了NULL,此时需要连接p,即r->next=p
分析:时间复杂度:O(n),空间复杂度O(1)。
到此为止,自己实现的思路全部写完,后面是看题解与评论上的一些思路,并对他们的代码做了一些优化后呈现出来的。
3.3 方法三
第82题用到了递归法,这道题也可以!
思想就是如果当前节点与后一节点相同,那么递归后一节点,直到找到不同的节点,并让前一节点(对应前面提到的当前节点)指向后一节点,依次递归下去,如果递归过程中当前节点与后一节点不同,直接链接,最后的head就是最终结果。
ListNode* deleteDuplicates2(ListNode* head) {
if(head==NULL || head->next==NULL) return head;
ListNode *p = head->next;
if(head->val == p->val) {
while (p&&head->val==p->val){
p=p->next;
}
head=deleteDuplicates2(p);
} else {
head->next=deleteDuplicates2(p);
}
return head;
}
时间复杂度O(n),空间复杂度O(1)。
3.4 方法四
这个方法比较传统,在题解中空间复杂度为O(n),这里我将其进行优化,最终的时间复杂度为O(n),空间复杂度为O(1)。
思想是使用快慢指针,用慢指针跳过那些重复数,慢指针指的的元素就是返回链表中的元素。
ListNode* deleteDuplicates3(ListNode* head) {
if(head==NULL || head->next==NULL) return head;
// 1 1 2 2 3
ListNode *p=head,*q=p->next,*pre=head;
// 确定返回节点的头在哪里!
bool isonce=true;
while(p) {
if(q&&p->val==q->val) {
int tmp = p->val;
while(p&&p->val==tmp)
p=p->next;
if(p) q=p->next;
} else {
// 1 2 3 1 1 2 3
if (isonce) {
head = p;
isonce = false;
pre = head;
} else{
pre->next = p;
pre = p;
}
p=q;
if(q)
q=q->next;
}
}
if(isonce) {
head=NULL;
isonce=false;
} else {
pre->next=p;
}
return head;
}