题目:24.两两交换链表中的节点、19.删除链表的倒数第N个节点、160.链表相交、142.环形链表II
参考链接:代码随想录
24.两两交换链表中的节点
思路:这题就正常画图思考,要使用虚拟头结点简化操作。遍历时两个一组考虑。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode *dummyNode = new ListNode(0);
dummyNode->next=head;
ListNode *p=dummyNode;
ListNode *q=head;
ListNode *tmp;
while(q && q->next){//每次遍历时,p为结点1的pre,q指向结点1,必须结点2不为空,才能完成交换
tmp=q->next;
p->next=tmp;
q->next=tmp->next;
tmp->next=q;
p=q;
q=q->next;
}
head=dummyNode->next;
delete dummyNode;
return head;
}
};
标答没有用tmp,用的->next->next的方式,原理是一样的,而且标答没有删掉虚拟节点,我觉得不够严谨。
19.删除链表的倒数第N个节点
思路:这里一开始的想法是先获得链表长度,然后算出正数的节点数,然后遍历,需要两次遍历。对于一次遍历的方法,我想到的使用双指针,既然对倒数第n个节点,即需要删除的节点,和最后一个节点之间的距离为n-1,故我们一开始用p指针指向头,q指针指向p往后n-1距离的位置,然后同时开始移动p和q,直到q到达末尾,此时p节点就是需要删除的节点。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyNode = new ListNode(0);
dummyNode->next=head;
ListNode* p = dummyNode;
ListNode* q = dummyNode;
for(int i=1;i<n;i++){
q=q->next;
}
ListNode* p_pre=dummyNode;//这个用于保存p的前置节点
while(q->next){
p_pre=p;
p=p->next;
q=q->next;
}//这里q已经完成了它的使命,剩下我们操作p即可
ListNode* tmp=p;
p_pre->next=p->next;
delete tmp;
head = dummyNode->next;
delete dummyNode;
return head;
}
};
这题思路并不难,但具体代码实现的时候debug了很久才完成,主要是在q到达末尾后我还想着操作q,实际上此时q已经完成任务,剩下就是一个简单的删除,依靠p->pre和p即可完成。标答没有写内存释放,其实我们在力扣中不写也可以,后面为简化操作我就不写了。
标答:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* slow = dummyHead;
ListNode* fast = dummyHead;
while(n-- && fast != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
// ListNode *tmp = slow->next; C++释放内存的逻辑
// slow->next = tmp->next;
// delete nth;
return dummyHead->next;
}
};
标答中的slow相当于我们代码中的p_pre,fast相当于我们代码中的q->next
160.链表相交
思路:我看到的第一思路是首先计算两条链表的长度,然后得到长度差,然后让那条长的指针先走几步,等两个指针平齐的时候,再同时走,直到找到相同节点。解析也是这样作答的。
class Solution {
public:
int getLength(ListNode* head){
int len=0;
ListNode* p=head;
while(p){
len++;
p=p->next;
}
return len;
}
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA=getLength(headA);
int lenB=getLength(headB);
ListNode *p=headA;
ListNode *q=headB;
if(lenA>lenB){
for(int i=1;i<=lenA-lenB;i++){
p=p->next;
}
}
else{
for(int i=1;i<=lenB-lenA;i++){
q=q->next;
}
}//使p和q对齐
while(p && q && p!=q){
p=p->next;
q=q->next;
}
return p;
}
};
和标答比,我的代码将获得长度写成了一个函数,更加简洁。
142.环形链表II
思路:本题一开始我没想到思路,主要是判断环的方法我想不到,所以直接看了解析。
慢节点p一次走一个节点,快节点q一次走两个节点,这样如果有环,必定相遇。相遇时,p走了x+y,q走了x+y+n(y+z),其中n为q转过的圈数,由于快指针走过的路程是慢指针两倍,有2(x+y)=x+y+n(y+z),需要求x,得到x=(n-1)(y+z)+z,其中n>=1,由图可以看出,再选两个指针r和s分别指向head和相遇节点,同时往后走,最后相遇的节点就是入口节点。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* p=head;
ListNode* q=head;
while(q && q->next){//快指针不能到达空
p=p->next;
q=q->next->next;
if(p==q){//相遇
ListNode *r=head;
ListNode *s=p;
while(r!=s){
r=r->next;
s=s->next;
}
return r;
}
}
return nullptr;//跳出循环一定是遇到了空
}
};
本题思路比较复杂,而且需要数学运算,初次见到不好想到思路。我除了思路外,另一个启示是在写代码方面可以将真正需要的返回结果放到while中间进行,而不是仅仅把while用来遍历。