前言
今天的题目包括链表基本的查找、交换、循环。
在很多细节方面我出现或多或少的问题。例如while循环的终止条件、如何避免陷入死循环。
24.两两交换链表中的节点
思路
首先确定好链表元素替换的方法,先用cur指向dummyhead,用*tmp1保存cur->next,tmp2保存cur->next->next->next.
再开始更改元素位置,一定要注意这里的tmp1和tmp2不可以同时作为两个指针的右值,否则会死循环!
最后更改成功了,cur指针就可以进行下一步cur=cur->next->next->next啦!
代码
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
ListNode* cur = dummyHead;
while(cur->next != nullptr && cur->next->next != nullptr) {
ListNode* tmp = cur->next; // 记录临时节点
ListNode* tmp1 = cur->next->next->next; // 记录临时节点
cur->next = cur->next->next; // 步骤一
cur->next->next = tmp; // 步骤二
cur->next->next->next = tmp1; // 步骤三
cur = cur->next->next; // cur移动两位,准备下一轮交换
}
return dummyHead->next;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
总结
这里的while循环的终止条件是(cur->next ==NULL||cur->next->next ==NULL),因为当链表长度为偶数时刚好元素全部进行过交换,cur->next刚好为空;而链表为奇数时还留下一个元素,也不需要交换。
19.删除链表的倒数第N个节点
思路
用双指针法最适合找到“倒数”第几个结点。
用指针fast和slow来查找,首先将他们都指向dummyHead。
例找倒数第二个结点。则fast在slow指针前2个位置,但因为删除fast的前倒数第二个结点,为了找到其位置,slow节点应找倒数第三个结点,凭其next指针寻找倒数第二个结点地址。注意在移动fast指针时应判断fast!=NULL,因为访问NULL时会报错。
接下来就是移动操作,等fast==NULL时便找到了倒数第三个指针位置。
再进行删除操作。这个很简单,不作赘述。
代码
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;
}
}
时间复杂度: O(n)
空间复杂度: O(1)
总结
这种查找链表最好是创造一个虚拟头结点,历遍起来更方便。同时要记得while的终止条件应是fast!=NULL哦,我常常搞不懂这种问题。
07. 链表相交
思路
从上图可以发现链表到最后会变成一体,那不如反推,将链表a和链表b尾部对齐
这样就完成啦。
代码
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0;
while (curA != NULL) { // 求链表A的长度
lenA++;
curA = curA->next;
}
while (curB != NULL) { // 求链表B的长度
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
swap (lenA, lenB);
swap (curA, curB);
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap--) {
curA = curA->next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != NULL) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
时间复杂度:O(n + m)
空间复杂度:O(1)
总结
一开始我没有理解题意,以为链表A、B只是短暂的相遇又分开,其实最后已经重合了。
142.环形链表II
思路
这是一个理解起来稍微有一点难度的题目,不过卡哥太强了一讲就清晰了。
先让fast一步走两个结点,slow一步走一个结点,这样他们的相对速度为1,才能保证相遇。
当快慢指针相遇时:
假设slow刚走了(x+y),fast走了( x+y+n(y+z) );
则 2*(x+y)=( x+y+n(y+z) );
化简得 x = (n - 1) (y + z) + z;
不管n为多少,x和z定一样长,即他们在环形入口相连,所以确定slow和fast的相遇点,当slow从head出发,fast从相遇点出发,他们一定在同一时间到达环形入口点,这样问题就迎刃而解啦。
代码
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
// 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2; // 返回环的入口
}
}
return NULL;
}
};
时间复杂度: O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
空间复杂度: O(1)
总结
一刷:又是让人头疼的while终止条件,当然是fast打头阵啦,当fast!=NULL&&fast->next!=NULL就好啦。这是超级经典的循环链表问题,一定要记住哦。
二刷:再刷发现自己都忘的差不多了,简单复习了一下,环形链表问题还需要深入了解。