目录
一、题目描述
方法一:扭动箭头
思路:
注意点:
代码:
代码解析:
1.
2.
优化代码:
注意:
1.
2.
方法二:头插
1.介绍头插
2.解决思路
3.代码
4.注意点
总结:
需要注意的是:
一、题目描述
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2] 输出:[2,1]
示例 3:
输入:head = [] 输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000]
-5000 <= Node.val <= 5000
方法一:扭动箭头
思路:
通过三个指针prev 、 next 、 cur。利用cur遍历节点,实现指针箭头的转向。
其中prev和cur用来反转,next用来迭代!
注意点:
1.需要注意的是,利用了指向第三个节点的指针next,因此要保证至少有两个节点。
注意题目中给出的范围,当只有一个或者两个节点的时候,直接返回即可。
2.做链表题,一定要注意链表的头、尾节点。next == null时,代表cur指针到达了尾节点。
代码:
struct ListNode* reverseList(struct ListNode* head) {
if(head == NULL)
return head;
struct ListNode* prev = head;
struct ListNode*cur = head->next;
if(cur == NULL)
return head;
struct ListNode* next = cur->next; //既然指向第三个节点的指针,则一定要考虑只有一个节点或者没有节点的情况!
prev->next = NULL;
while(cur)
{
if(next == NULL){
cur->next = prev;
break;
}
else{
cur->next = prev;
}
prev = cur;
cur = next;
next = cur->next;
}
return cur;
}
代码解析:
1.
if(head == NULL) //没有元素
return head;
if(cur == NULL) //只有一个元素
return head;
对于内存、野指针问题,在链表中一定要十分谨慎!
2.
if(next == NULL){
cur->next = prev;
break;
}
else{
cur->next = prev;
}
prev = cur;
cur = next;
next = cur->next;
可以观察到,当遍历节点的时候,if和else语句只需要完成指针的指向改变功能即可。
其余的语句可以封装在外部,作为循环的控制条件。
对于循环之中的迭代,往往位于外部,控制循环的运行。if语句的内部用来进行功能的实现。
优化代码:
直接让prev本身就是空
struct ListNode* reverseList(struct ListNode* head) {
if(head == NULL)
return head;
struct ListNode* prev = NULL;
struct ListNode* cur = head;
struct ListNode* next = cur->next;
while(cur)
{
cur->next = prev;
prev = cur;
cur = next;
if(next)
next = next->next;
}
return prev;
}
解析:cur正常后移(不断追赶next)。
当next为空时,next不在后移,cur后移追赶next到空,循环结束。
注意:
1.
链表题目!!!一定要对代码的整体逻辑的先后顺序理清楚!!!
如:此代码
先改变指向,prev追cur,再cur追赶next,再next后移。
当对整体逻辑理清楚之后,代码变得更加简洁。
2.
当存在三个指针的时候,尝试利用画图,对初始指针(如prev)进行节点的位移,会使代码更简洁。
方法二:头插
1.介绍头插
头插即头部插入
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead); // 链表为空,pphead也不为空,因为他是头指针plist的地址 //pphead 要断言 , 因为需要对 pphead 进行解引用操作并修改 *pphead = ....
//assert(*pphead); // 不能断言, 链表为空,也需要能插入 //因为 *pphead 不需要再次解引
SLTNode* newnode = BuyLTNode(x);
newnode->next = *pphead; //为空可以插入
*pphead = newnode; //新头指向最开头的节点
}
2.解决思路
取一个新指针newhead,去引领一个新的地址。利用头插,取原链表的节点,插入到头部。
3.代码
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* newhead = NULL;
struct ListNode* cur = head;
if(head == NULL)
return NULL;
struct ListNode* next = head->next; //需要十分敏感!当需要用到next此类的指针的时候,一定要警惕cur是不是空指针!
while(cur)
{
cur->next = newhead;
newhead = cur;
// cur = cur->next; //不对,cur->next是空!
cur = next;
if(next)
next = next->next;
}
return newhead;
}
4.注意点
1.struct ListNode* next = head->next; //需要十分敏感!当需要用到next此类的指针的时候,一定要警惕cur是不是空指针,防止出现野指针!!!
2. // cur = cur->next; //不对,cur->next是空!
由于第一步中已经将cur指向newhead,所以cur的next是空!
画图时,需要对原链表和newhead进行同时标注cur位置!
3.if(next)
最后一步next不能后移,否则会出现野指针!
总结:
方法一:需要利用prev 、 cur 、 next三个指针,来进行箭头逆置(cur指向prev)与迭代(cur、prev、next不断往下遍历)。
方法二:需要利用头插的思想完成每个节点的插入。
方法一和方法二,都是从原节点头部开始取节点,依次往下进行!
需要注意的是:
对于链表的题目,头插和尾插的思想十分常见。当头插行不通时,常常考虑尾插;尾插行不通,常常考虑头插。
头插:取新节点,利用头插的方式,插入到newhead的头部。常用到newhead。
尾插:取新节点,利用尾插的方式,插入到newhead链表的尾部。常用到newhead。
无论头插还是尾插,对于单链表而言,都是需要从原链表的开头位置开始取节点。(源节点的链接顺序是从头链接至尾,而不是从尾链接至头)
对于尾插,若想详细了解,见下方博客
用尾插的思路实现 “合并两个有序链表”-CSDN博客
用尾插的思想实现移除链表中的元素-CSDN博客(推荐)