一、节点的定义
struct ListNode{
int val;
ListNode* next;
ListNode(int a):val(a),next(NULL){}
};
二、 翻转的方法
1、迭代
- 在遍历列表时,我们需要将当前节点的 next 指针改为指向前一个元素
- 前一个元素没有,所以要先存储其前一个元素
- 在更改引用之前,还需要另一个指针来存储后一个节点
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr) {
ListNode* next = curr->next; // 存储当前节点的下一个节点
curr->next = prev; // 更改当前节点指向的元素
prev = curr; //当前节点赋值给前一个节点
curr = next; // 下个节点赋值给当前节点
}
return prev;
}
};
注意,在反转链表时要先确定一个pre节点,表示当前节点的前一个节点。初始时pre为空指针。在循环中,需要先将下一个节点next保存下来,以便于后续操作,然后将当前节点的next指向pre,反转当前节点。之后要更新pre和cur,使得下一次循环可以正确进行。循环一直进行到cur为nullptr,此时链表就被反转了。
2、就地反转法
把当前链表的下一个节点pCur插入到头结点dummy的下一个节点中,就地反转。
dummy->1->2->3->4->5的就地反转过程:
dummy->2->1->3->4->5
dummy->3->2->1->4->5
dummy->4>-3->2->1->5
dummy->5->4->3->2->1
过程:
- prev连接下一次需要反转的节点
- 反转节点pCur
- 纠正头结点dummy的指向
- pCur指向下一次要反转的节点
class Solution {
public:
ListNode *reverseList(ListNode *head)
{
if (head == nullptr)
return head;
ListNode dummy = ListNode(-1);
dummy.next = head;
ListNode* pre = head; // 前一个要反转的节点
ListNode* cur = head->next; // 当前要反转的节点
while (cur) {
pre->next = cur->next;
cur->next = dummy.next;
dummy.next=cur;
cur=pre->next;
}
return dummy.next;
}
};
3、递归
- 使用递归函数,一直递归到链表的最后一个结点,该结点就是反转后的头结点,记作 ret .
- 此后,每次函数在返回的过程中,让当前结点的下一个结点的 next 指针指向当前节点。
- 同时让当前结点的 next 指针指向 NULL ,从而实现从链表尾部开始的局部反转
- 当递归函数全部出栈后,链表反转完成。
递归解题首先要做的是明确递推公式的含义,在这里对于结点1来说,它只需要知道它之后的所有节点反转之后的结果就可以了,也就是说递推公式reverseList的含义是:把拿到的链表进行反转,然后返回新的头结点。
到这里,就可以写成如下的代码了。
ListNode* reverseList(ListNode* head) {
// 调用递推公式反转当前结点之后的所有节点
// 返回的结果是反转后的链表的头结点
ListNode* newHead = reverseList(head->next);
}
接着要做的就是反转结点1,也就是将head指向的结点作为其下一个结点的下一个结点,即head.next.next=head。
最后,将head指向的结点的下一个结点置为nullptr,就完成了整个链表的反转。
代码如下
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr ) {
return head;
}
ListNode* ret = reverseList(head->next);
head->next->next = head;
head->next = nullptr ; // 因为此时的head,最终是新链表的尾部,所以要让它指向Null
return ret;
}
};
参考:
看图理解单链表的反转 - ma_fighting - 博客园