反转链表
- 题目描述
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
数据范围: 0≤n≤1000
要求:空间复杂度 O(1) ,时间复杂度 O(n) 。
例如当输入链表{1,2,3}时,经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
第一种做法:改值法
-
解题思路
👊万事先考虑特殊情况【空链表】,此时直接返回空链表就行。
我们对链表操作时一定要记住,链表不是数组,每个节点看起来是连在一起的, 但在实际物理空间中(也就是在内存中并非连续存储的,如果失去了指针next
你就找不到“下一个”节点了!)
改值法的核心是——两边向中间渗透,利用两个指针tail
和phead
将链表的值进行swap交换。-
预处理:
tail
指针指向链表尾,phead
指向链表头 -
明确指针移动多少步。我们需要让
tail
和phead
相逢但是不相遇。
⭕️重点在for
循环的控制上。
我们phead
需要移动 l e n g t h 2 − 1 \frac{length}{2}-1 2length−1次,
tail
要往前移动一格,怎么移动?
我这里是先把tail
放到phead
的位置,再往后移动 l e n g t h − 2 ∗ ( i + 1 ) length-2*(i+1) length−2∗(i+1)
PS:这里的i
是外层循环phead
的移动步数控制,但是由于我设置的初始值为0
,所以需要利用(i+1)
来控制tail
的移动。
🌰举个栗子:
如果不理解可以进行自己画一个链表示意图出来推算一下,假设这里有单链表{1,2,3,4,5,6}
1️⃣第一层循环i=0
中,tail
指向 6 6 6,phead
指向 1 1 1;
swap交换数值;
tail
先拿到phead
的位置,即指向 1 1 1,为了让他指向 5 5 5,我们要移动4次;
紧接着将phead
往后移一格,指向 2 2 2;
2️⃣进入第二层循环i=1
中,tail
指向 5 5 5,phead
指向 2 2 2;
swap交换数值;
tail
先拿到phead
的位置,即指向第二格,为了移动到倒数第三格,即指向 4 4 4,我们要移动2次;
再把phead
往后移一格,指向 3 3 3; -
实现数值交换【就是“如何交换两个水杯中的可乐和橙汁”的问题】
借助一个辅助变量存储一杯水即可。
-
- 完整代码
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @return ListNode类
*/
ListNode* ReverseList(ListNode* head) {
// write code here
if(head==nullptr){
return head;
}
ListNode* tail=head,*phead=head;
int length=1;
//将尾指针表示出来
while(tail->next!=nullptr){
tail=tail->next;
length++;
}
//向中间扩散
for(int i =0 ;i<= (length/2-1);i++){
int t=phead->val;
phead->val=tail->val;
tail->val=t;
//先把tail拿回来!
tail=phead;
//挪到倒数第i个位置【重点】
for(int j=0;j<(length-2*(i+1));j++){
tail=tail->next;
}
phead=phead->next;
}
return head;
}
};
第二种做法——修改指针方向【强烈安利!】
- 解题思路
这个就更好理解指针的寓意了!💯
【突破口】尾指针的next
必是NULL
。
我们都知道单链表没了next
就是断掉了,节点在内存中不连续存储就导致我们失去某个节点的next
就找不到该节点的下一个节点了❗️
这意味着该节点后边的所有节点你都找不到了!所以在“断开”链表的时候一定要谨慎啊!!!
于是我就想到了,我从尾节点开始向链表头的方向修改指针方向,因为尾节点它的next
没有东西。- 预处理:
tail
指向链表尾,phead
指向链表倒数第二格,即tail
前面一格的节点。 new_head
存储修改方向后的链表头。这个是我们最后的返回值!- 考虑循环结束条件。【当
phead
移动到原始链表的头部head
的时候就证明我们所有节点的指针方向next
都反向了】 - 修改所有节点指针方向。
核心在于tail->next=phead
将指针反向;
然后tail=phead
将tail
指针移动到phead
处,也就是tail
向前移动一格;
再将phead
移动到tail
的前一格,循环往复至原链表头head
即可。
- 预处理:
- 完整代码
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
#include <cstddef>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @return ListNode类
*/
ListNode* ReverseList(ListNode* head) {
// write code here
//如果是空链表直接返回空!
if(head==nullptr){
return head;
}
ListNode* tail=head,*phead=head;
//将尾指针表示出来
while(tail->next!=nullptr){
tail=tail->next;
if(phead->next->next==nullptr) break;//把phead挪到尾指针前面
phead=phead->next;
}
ListNode* new_head=tail;
while(phead!=head){
tail->next=phead;
//别忘了把tail挪到phead的位置
tail=phead;
phead=head;
while(phead->next!=tail){
//再次将phead挪到尾指针前面
phead=phead->next;
}
if(phead==head){
tail->next=phead;
phead->next=NULL;
break;
}
}
return new_head;;
}
};
虽然难度是“简单”,但是指针的操作更熟悉了!加油加油!!小白打怪升级!