题目
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4,
应该返回 2->1->4->3.
解决方案
首先确定结果头结点的位置,分三种情况
- 如果head是空,那么直接返回空指针;
- 如果head->next为空,那么只有一个head有效节点,直接返回head;
- 当head->next不为空,因为前两个一定交换,那么结果头结点res一定指向head->next;
其中,前两种情况可以合并。
原始 | 1 | 2 | 2 | 4 | |
两两交换 | 2 | 1 | 4 | 3 |
重点:两组之间连上 :即1和4连上
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* res; //结果的头结点
if(!head || !head->next) return head;
else res = head->next;
ListNode* it = head; //注释1
ListNode *end = it; //交换之后的尾部,为了方便与下一对交换之后的头部相连
ListNode *temp = nullptr;
while(it && it->next) //注释2
{
if(end != it) end->next = it->next; //注释3
temp = it->next;
it->next = temp->next;
temp->next = it;
end = it;
it = it->next;
}
return res;
}
};
解释
- 注释1:为什么要用个it指向head,而不直接用head进行移动?
首先,用head移动是可以的,但是head的话这个词很容易造成误导,词不达意,等一段时间后再看自己的代码,也需要花时间去理解head是中间变量;但是如果用it,很明显是一个临时变量,和temp一样明显,便于理解和阅读,希望大家都能养成好习惯。 - 注释2:为什么用 it && it->next?
因为&&(且)运算符是有顺序的,如果前一个为false,则不会判断&&后面的表达式;
所以如果head为空,那么&&前的表达式结果为false,就不会去判断&&后面的表达式head->next,
因此就不会出现报错:member access within null pointer of type 'struct ListNode (试图访问空指针)的情况。
并且这也是判断后续有效结点是否超过两个,超过两个的情况需要交换后续结点,否则不需要。
例如(1 ->2 ->3) 经过一次while循环。此时已经变成(2 ->1 ->3)不需要其他操作。
- 注释3: if (end != it) end->next = it->next 的作用
(1 2 3 4为例)
第一次while循环时:end 和 it在同一位置(1),if 判断为false,只进行下面的交换操作,变成(2-> 1 3-> 4)
第二次while循环时:end指向(1),it 指向(3),此时 if 判断为true,将1和 3后面的4相连,再交换 3 4变成 (2->1->4->3)
以后的while循环同理。
题解上看见的大神的代码---------双重指针法(呜呜呜我果然还是不行吗)
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode**it=&head; //注释1
ListNode *first,*second;
while((first=*it)&&(second=first->next))
{
first->next=second->next;
second->next=first;
*it=second;
it=&(first->next); //注释2
}
return head;
}
};
解释
-
注释1: ListNode**it = &head
可以修改头指针,it是一个指向head的指针,所以可以通过it修改head的值,上一个解法中 *it=head的话不能修改head的值
通过这种方式来不对头结点单独处理,所以不需要上一个方法的找头结点res的步骤。 -
注释2: it = & ( first->next)
这样的话可以直接修改 first->next 的值,通过 it 而不需要保存 first 的值。 -
个人想法:双重指针没有一定基础还是不乱用了,写完之后怎么好读怎么写。