单向链表反转(迭代+递归)
之前刷题目的时候有刷到过,直接用了暴力,迭代的两种方法过了,就懒得看递归的方法了。结果在深圳场的平安普惠技术面试的时候,面试官正好问到这个题目,用迭代法回答了思路,又问有没有递归的思路,悔不当初啊!
题目描述: 输入一个链表,反转链表后,输出新链表的表头
- 暴力构造法: 链表内每一个节点都是一个简易的数据,直接用vector<ListNode*>将所有节点数据存储下来,利用vector的先进后出特性,重新构建一个新的链表,既是反转过后的链表。
时间复杂度:O(n),空间复杂度:O(n)
根据上述分析,得到下面的代码:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
vector<ListNode*> pv;
ListNode* p=pHead;
while(p){
pv.push_back(p);
p=p->next;
}
ListNode* NewpHead=NULL;
p=NewpHead;
while(!pv.empty()){
p=pv.back();
pv.pop_back();
p=p->next;
}
return NewpHead;
}
};
- 迭代翻转法: 链表节点的结构中next指针指向下一个节点,要想反转链表,即改变每个节点的next指针指向
需要三个额外的指针来保存原始链表和反转链表的结构:
front指针 指向当前需要调整next指针的节点
tile指针 指向NULL,是反转过后的链表的头节点
save指针 指向原始链表中待反转的第一个节点
每次迭代过程如图所示:
时间复杂度:O(n),空间复杂度O(1)
需要进行n次迭代,每次迭代中需要的时间是常数级别O(1),整个过程只需要三个额外的ListNode指针空间,也是常数级别O(1)。
根据上述分析,得到下面的代码:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(!pHead)
return NULL;
ListNode* front=pHead;
ListNode* tile=NULL;
ListNode* save=NULL;
while(front){
save=front->next;
front->next=tile;
tile=front;
front=save;
}
return tile;
}
};
- 递归翻转法: 递归法的两个基本要素:边界条件,递归模式,必须清晰明了。
边界条件: 反转单向链表的边界条件是当前节点已经为链表最末尾的节点,即
node->next==NULL
递归模式: 最重要的就是把大的问题变小化。如只考虑两个节点的反转,n1->n2->NULL,这时递归函数只要做两个事情:
a、n1->next->next=n1;
b、n1->next=NULL;
这样子就完成了两个节点的反转,得到链表n2->n1->NULL。
那么递归模式应该返回什么呢?
在n1->n2->n3->NULL链表中,经过最后两个节点的反转,变成
因此,每次反转过后,返回的应该是原始链表的最末尾节点(n3)。
时间复杂度:O(n),空间复杂度:O(n)
n个节点的链表,要递归n次;每次递归模式内,需要时间为常数级别,时间为O(1),都要创建O(1)的newpHead空间。
根据上述分析,得到下面的代码:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(!pHead || pHead->next==NULL)
return pHead;
ListNode* newpHead=ReverseList(pHead->next);
pHead->next->next=pHead;
pHead->next=NULL;
return newpHead;
}
};