24
讲解视频:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点_哔哩哔哩_bilibili
文章讲解:代码随想录 (programmercarl.com)
状态:没看讲解,能直接写出(画图直接模拟)。故相关代码可通过LeetCode记录或“文章讲解”查看。这题我没用到虚拟头结点,建议用视频讲的虚拟头结点比较好。
面试高频题。
!!!!用虚拟节点,记住下面这个过程,按过程写代码即可。
!!!(1)(2)(3)的顺序确认:
对于图中第二行,由于p->next即节点1已经被q标记,所以p->next没什么用了,所以第一步就是p->next=r;节点2已经被r标记,若先操作r->next=q,那么节点3将与前几个节点失去联系,故要先操作q->next再操作r->next。
因为可能链表只包含一个节点或者是空节点,所以在循环遍历前只初始化第一个节点p,q和r到循环内再定义。
注意while循环的条件。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* _dummyNode = new ListNode();
_dummyNode->next = head;
ListNode* p = _dummyNode;
while(p->next != nullptr && p->next->next != nullptr) {
ListNode* q = p->next;
ListNode* r = p->next->next;
p->next = r;
q->next = r->next;
r->next = q;
p = q;
}
return _dummyNode->next;
}
};
19 快慢指针
讲解视频:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点_哔哩哔哩_bilibili
文章讲解:代码随想录 (programmercarl.com)
快慢指针,好题目,值得记住。
状态:没看讲解,能直接写出(知道是双指针后突然想出,下次做不一定能想出,得复盘记住)。
细节tips(得记住,做删除结点题都有用):删除第i个结点时,要让指针指向第i-1个节点,故需要虚拟头结点_dummyHead。
思路:
让快指针fast、慢指针slow初始化都指向虚拟头结点_dummyHead,让快指针fast先移动n步,此时快慢指针距离为n,如下图所示。
- 让快慢指针同时移动,直到快指针指向了最后一个节点,此时慢指针指向了待删节点的前一个节点,如下图。
代码如下
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* _dummyHead = new ListNode(); //虚拟头结点
_dummyHead->next = head;
ListNode *slow = _dummyHead; //慢指针,初始化slow位置
ListNode *fast = _dummyHead; //快指针
while(n--) {fast = fast->next;} //初始化fast位置,思路第1步
while(fast->next != nullptr){ //跳出循环时,slow指向待删节点的前一个节点。思路第2步
slow = slow->next;
fast = fast->next;
}
ListNode *tmp = slow->next; //待删节点
slow->next = tmp->next;
delete tmp;
return _dummyHead->next;
}
};
面试题 02.07.
讲解视频:没有
文章讲解:代码随想录 (programmercarl.com)
状态:看了前面一部分讲解,能直接写出,估计自己想不出,当成不会做看待。
思路
- 求出两个链表的长度,并求出两个链表长度的差值x。
- 让长度较长的链表的头指针移动x步,移动后,两个头指针所处位置到最后一个节点的长度相等。
- 同时移动两个头指针,若两个头指针的值相等,说明指向同一块内存,即相交;若移动完都没出现值相等的情况,说明不相交。
代码如下,
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
//求链表A长度
int lenA = 0;
ListNode* cur = headA;
while(cur != NULL){
++lenA;
cur = cur->next;
}
//求链表B长度
int lenB = 0;
cur = headB;
while(cur != NULL){
++lenB;
cur = cur->next;
}
//若A比B长,先让A的头指针向后移(lenA-lenB)位
if(lenA > lenB){
int tmp = lenA - lenB;
while(tmp--) headA = headA->next;
}
else{//否则,让B的头指针向后移(lenB-lenA)位
int tmp = lenB - lenA;
while(tmp--) headB = headB->next;
}
//此时headA与headB的链表长度一致。同时移动,若而二者地址相同,则相交
while(headA != NULL){ //也可以headB!=NULL,二者肯定同时为NULL
if(headA == headB) return headA; //此时指向同一块内存,说明相交
headA = headA->next;
headB = headB->next;
}
return NULL; //不相交
}
};
142 环形链表:快慢指针
讲解视频:把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II_哔哩哔哩_bilibili
文章讲解:代码随想录 (programmercarl.com)
状态:不会做
这题其实有两问:①链表是否有环?②找到环的入口。
Q1:如何判断链表是否有环?
思路:用双指针,快指针以快的速度在走,慢指针以慢的速度在走,若快慢指针相遇说明有环。若没环,快慢指针永远不会相遇,快指针会先到终点。
细节:快慢指针都从起点出发,快指针每次走2个节点,慢指针每次走1个节点。在环内,快指针相对于慢指针移动来说,每次就移动一个节点(相对速度为1),所以它俩一定会在环内相遇,而不会令快指针跳过慢指针。tips:若快指针每次走3个节点,慢指针走1个,就可能出现“快指针跳过慢指针”的情况。
Q2:如何找到环的入口?
思路:可通过对题目建立数学等式来解。快慢指针相遇时如下图所示,
从图中可知:在相遇时,慢指针slow走了x+y步,快指针fast走了x+n(y+z)+y步(n为快慢指针相遇前快指针在环内走了n圈,n>=1),以上为路程,而它们的速度分别为1和2,由于二者走了相同时间,
可建立等式(x+y)/1=[x+n(y+z)+y]/2,化简得x=n(y+z)-y,即x=(n-1)(y+z)+z。若n=1,则x=z,说明相遇点到入口处的距离z=起始点到入口处的距离x。当然,n也可以不取1,就表示index2从起始点到入口处的过程中,index1从相遇点开始走,走了n-1圈后,再走距离z,就可以在入口处与index2相遇(注意:index1和index2速度相同,都为1步)。—>这么写代码,不就可以找到入口处了嘛!
tips:!!慢指针在入环后,一定走了不到一圈就被快指针给追上了,原因可见讲解视频的12:25。
代码如下,
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head;//慢指针
ListNode *fast = head;//快指针
while(fast != NULL && fast->next != NULL){//!由于快指针走的比较快,故以它为判断条件;且快指针每次走两步,所以fast!=NULL判断第一步,fast->next!=NULL判断第二步。
fast = fast->next->next;//快指针每次走两步
slow = slow->next;//慢指针每次走一步
if(fast == slow){//两个指针在相遇点相遇时
ListNode *index1 = fast;//index1从相遇点开始走,这里也可以=slow
ListNode *index2 = head;//index2从起始点开始走
while(index1 != index2){//若有环,index1和index2一定会在入口处相遇
index1 = index1->next;
index2 = index2->next;
}
return index1;//相遇点,这里也可以是index2
}
}
return NULL;
}
};