一、反转链表
解法一:迭代(推荐)
- 优先处理空链表,空链表无需反转
- 设置前序和当前指针
- 遍历链表,每到一个节点,断开当前节点与后一节点的指针,用临时变量记录后一节点,让当前节点指向前一节点
- 更新前序与当前指针
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(pHead==NULL||pHead->next==NULL)
return pHead;
ListNode*pre=NULL,*cur=pHead;
while(cur)
{
//断开链表,要记录后续一个
ListNode*tmp=cur->next;
//当前的next指向前一个
cur->next=pre;
//前一个更新为当前
pre=cur;
//头节点更新为其原下一节点
cur=tmp;
}
return pre;
}
};
图示:
时间复杂度O(n),遍历链表一次
空间复杂度O(1),未使用额外空间
解法二:递归
- 对每个节点递归向后遍历
- 然后向前依次逆转两个节点
- 连接逆转后的尾节点和这一级的节点
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
//这里必须都判断,后续程序使用了pHead->next->next=pHead,不判断下一节点是否为空,直接运行会报错
if(pHead==NULL||pHead->next==NULL)
return pHead;
//反转下一个
ListNode*newHead=ReverseList(pHead->next);
pHead->next->next=pHead;//逆转操作
pHead->next=NULL;//断开原有指向,设置空节点
return newHead;
}
};
做这类题目优先考虑特殊情况,当头指针为空,或其next指针为空时,反转后结果为其本身,直接返回本身
pHead->next->next=pHead;
这一步是让下一个节点的next指针指向该节点,通过递归实现指向的全部反转,因此前置判断要加上pHead->next==NULL
,以避免对NULL操作报错
时间复杂度O(n),递归遍历链表一次
空间复杂度O(n),递归栈深度为链表长度n
解法三:借用容器
这属于暴力求解了,可以用多种容器实现,
可以进行链表交换,也可以进行链表值的交换来完成题目,不推荐
用vector容器
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
vector<ListNode*>v;
while(pHead)
{
v.push_back(pHead);//存储指针节点
pHead=pHead->next;
}
reverse(v.begin(),v.end());,//容器内反转
ListNode*newHead=v[0];//设置头尾
ListNode*end=newHead;
for(int i=0;i<v.size();i++)
{
end->next=v[i];//进行链表创建
end=end->next;
}
end->next=NULL;
return newHead;
}
};
用stack,和vector一样,不在多说
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
stack<ListNode*>s;
while(pHead)
{
s.push(pHead);
pHead=pHead->next;
}
if(s.empty())
return NULL;
ListNode*newHead=s.top();
s.pop();
ListNode*end=newHead;
while(!s.empty())
{
end->next=s.top();
s.pop();
end=end->next;
}
end->next=NULL;
return newHead;
}
};
重新构建链表
时间复杂度O(n),遍历链表及容器n的常数次
空间复杂度O(n),额外使用空间存值
二、指定区间内反转链表
解法一:迭代(推荐)
- 使用虚拟头部,便于表头位置的反转
- 使用前序与当前指针
- 遍历到m位置
- 对m到n位置的节点,依次断掉指向后续的指针,反转其指向
class Solution {
public:
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
ListNode* reverseBetween(ListNode* head, int m, int n) {
// write code here
//非必要判断
if(head==NULL||head->next==NULL||m==n)
return head;
//创建一个哨兵位,虚拟节点,这个赋值不知到有啥讲究,这里是赋为-1
ListNode*dummy=new ListNode(-1);
dummy->next=head;
ListNode*pre=dummy,*cur=head//设置前序节点和当前节点
for(int i=0;i<m-1;i++)//找到m节点
{
pre=cur;
cur=cur->next;
}
for(int i=m;i<n;i++)//反转m到n的链表
{
ListNode*tmp=cur->next;
cur->next=tmp->next;
tmp->next=pre->next;
pre->next=tmp;
}
return dummy->next;
}
};
在全部反转的基础上,设置区间,先找到区间头节点,再进行区间反转
时间复杂度O(n),最坏情况下遍历全部链表
空间复杂度O(1)
解法二:递归
- 按照第一个递归缩短子问题找到反转起点
- 按照第二个递归缩短终点的子问题,从第n个位置反转,向上拼接
class Solution {
public:
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
ListNode*tmp=NULL;
ListNode*reverse(ListNode*head,int n)
{
if(n==1)//只颠倒第一个节点
{
tmp=head->next;
return head;
}
//进入子问题求解
ListNode*node=reverse(head->next,n-1);
head->next->next=head;//反转
head->next=tmp;//拼接
return node;
}
ListNode* reverseBetween(ListNode* head, int m, int n) {
// write code here
//非必要判断
if(m==n||head==NULL||head->next==NULL)
return head;
if(m==1)//从第一个节点开始反转
return reverse(head,n);
//缩小子问题,我们只考虑p是反转后的链表的头就行
ListNode*p=reverseBetween(head->next, m-1, n-1);
//拼接已反转的部分
head->next=p;
return head;
}
};
时间复杂度O(n),最坏遍历全部链表
空间复杂度O(n),递归栈深度最坏为n
三、 链表中的节点每k个一组翻转
解法一:查找反转
class Solution {
public:
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
ListNode* reverseKGroup(ListNode* head, int k) {
// write code here
if(k<=1||head==NULL||head->next==NULL)
return head;
ListNode*node=head;
int len=length(head);
int part=len/k;//算出分为的组数
//设置虚拟节点
ListNode*result=new ListNode(-1);
ListNode*cur=result;
for(int i=0;i<part;i++)//外循环设置k个反转次数
{
ListNode*tmp=NULL;
for(int j=0;j<k;j++)//每k个进行反转
{
ListNode*ret=head->next;
head->next=tmp;
tmp=head;
head=ret;
}
cur->next=tmp;//拼接反转部分
while(cur->next)
cur=cur->next;//更新到尾节点的位置,为下一轮拼接做准备
}
//此时的头部已然为空
cur->next=head;
return result->next;
}
int length(ListNode*p)//计算节点个数
{
ListNode*cur=p;
int count=0;
while(cur)
{
count++;
cur=cur->next;
}
return count;
}
};
时间复杂度O(n),虽然使用双层循环,但最坏只是遍历了链表一遍
空间复杂度O(1)
解法二:递归(推荐)
class Solution {
public:
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
ListNode* reverseKGroup(ListNode* head, int k) {
// write code here
ListNode*tail=head;//找到每次反转的尾部
for(int i=0;i<k;i++)//遍历k次到尾部
{
if(tail==NULL)//到不了尾部直接返回
return head;
tail=tail->next;
}
ListNode*pre=NULL,*cur=head;//反转需要前序和当前节点
while(cur!=tail)//在到达当前尾节点前循环反转
{
ListNode*tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
head->next=reverseKGroup(tail,k);//进行下一组的反转,同时实现拼接
return pre;
}
};
时间复杂度:O(n),一共遍历链表n个节点
空间复杂度:O(n),递归栈最大深度为n/k,也可看做O(1)
解法三:容器模拟
vector
这个就是把值存起来,反转每k个一组的数据,然后再重构
class Solution {
public:
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
ListNode* reverseKGroup(ListNode* head, int k) {
// write code here
if(k<=1||head==NULL||head->next==NULL)
return head;
ListNode*cur=head;
vector<int>v;
int count=0,index=0;
while(cur)
{
v.push_back(cur->val);
count++;
index++;
if(count==k)
{
reverse(v.begin()+index-k,v.begin()+index);
count=0;
}
cur=cur->next;
}
cur=head;
for(int i=0;i<v.size();i++)
{
cur->val=v[i];
cur=cur->next;
}
return head;
}
};
stack
这个这是把节点存起来,利用栈的特点,边存边拼接重构
class Solution {
public:
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
ListNode* reverseKGroup(ListNode* head, int k) {
// write code here
if(k<=1||head==NULL||head->next==NULL)
return head;
ListNode*p=new ListNode(-1);
ListNode*cur=p;
stack<ListNode*>s;
int count=0;
while(true)
{
for(int i=0;i<k;i++)
{
s.push(head);
head=head->next;
count++;
if(head==NULL)
break;
}
if(count==k)
{
while(!s.empty())
{
cur->next=s.top();
s.pop();
cur=cur->next;
cur->next=NULL;
}
}
if(head==NULL)
break;
count=0;
}
ListNode*end=NULL;
while(!s.empty())
{
end=s.top();
s.pop();
}
cur->next=end;
return p->next;
}
};
时间复杂度O(n)
空间复杂度O(n)
这三道题逐步提高难度,在不使用额外空间的条件下,递归是最适合不过的了