Day4 链表
LeetCode 24.两两交换链表中的节点【链表/递归】
本题还是有一定难度的,思考了很长时间没有翻明白指针的迭代方法,还需要好好学习和思考。
解法1:迭代
朴素解法,思路就是让cur指向2,然后再令2指向1,1再指向3,在此过程中由于指针的改变必须按照这个顺序进行,就会导致后面的节点丢失,为了防止出现这种情况,我们就要提前存储后面两个节点。有理有据,行云流水,令人拍案。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead=new ListNode(0);
dummyHead->next=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;
}
return dummyHead->next;
}
};
解法2:递归
链表用递归到底是哪些神人想出来的,简洁的代码却内藏玄机,搞明白思路之后更是佩服的五体投地orz。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head==nullptr || head->next==nullptr) return head;
ListNode* cur=head->next;
ListNode* tmp=head->next->next;
cur->next=head;
head->next=swapPairs(tmp);
return cur;
}
};
LeetCode 19.删除链表的倒数第N个节点【双指针/递归】
感受我的痛苦☹,这道题的想法很精妙,但是细节也很多,有很多需要特殊考虑的地方,否则就会出现空指针异常。
解法1:双指针
首先这个找到倒数第n个节点的方法就很精妙:用快慢两个指针,快指针先走n步,然后快慢指针一起走,当快指针走到末尾时,慢指针恰好走到倒数第n的节点。因为要删除这个节点,就要找到这个节点前一个节点,所以在快指针走的时候,要少走一步,当快指针的next为空时就要停止。
但这也带来了问题,因为我们要多向前看一步,就必须保证快指针不为空,所以当链表为空,或者链表仅有一个元素时,都要提前判断,返回空指针。另外,如果要删除的是第一个元素节点,也要特殊判断,因为这会导致快指针提前走到末尾,导致空指针异常,所以这个也要提前判断,返回head->next即可。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head==nullptr || head->next==nullptr) return nullptr;
ListNode* cur=head;
ListNode* tmp=head;
while(n--) tmp=tmp->next;
if(tmp==nullptr) return head->next;
while(tmp->next!=nullptr){
tmp=tmp->next;
cur=cur->next;
}
cur->next=cur->next->next;
return head;
}
};
解法2:递归
递归回溯,好好看,好好学。感觉开启了新大陆。
class Solution {
public:
int cur=0;
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head==nullptr) return NULL;
head->next=removeNthFromEnd(head->next,n);
cur++;
if(n==cur) return head->next;
return head;
}
};
面试题02.07 LeetCode 160.相交链表【链表】
这道题还是很有研究的价值的,两个链表的交点并不是数值相等就是交点,需要指针相等才行。
解法1:统一起跑线
如果链表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){
lenA++;
curA=curA->next;
}
while(curB!=NULL){
lenB++;
curB=curB->next;
}
curA=headA;
curB=headB;
if(lenB>lenA){
swap(lenA,lenB);
swap(curA,curB);
}
int gap=lenA-lenB;
while(gap--) curA=curA->next;
while(curA!=NULL){
if(curA==curB) return curA;
curA=curA->next;
curB=curB->next;
}
return NULL;
}
};
解法2:走你曾走过的路
与前一种方法思路类似,既然两个链表的长度存在差值,那我们就让两个指针同时从各自的头结点出发,A如果走到链表末尾,就从B链表的开头继续走,同理B走到末尾也从A的开头继续走,当长链表走到短链表头的时候,长度差就消除了,我们继续走,一定会走到指针重合的相交点。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(!headA || !headB) return NULL;
ListNode* curA=headA;
ListNode* curB=headB;
while(curA!=curB){
curA=curA?curA->next:headB;
curB=curB?curB->next:headA;
}
return curA;
}
};
LeetCode 142.环形链表II【哈希表/双指针(数学题)】
又是一道非常经典的题目,当前进的路开始转圈,我们怎样才能找到离开循环的出口呢?
解法1:快慢指针
快指针一次走两步,慢指针一次走一步,两个指针一定会在环里相遇。然后就是一道数学问题。
来源:代码随想录,卡神牛!
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y ,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 x = z,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
动画如下:
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
由此可得代码:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast=head;
ListNode* slow=head;
while(fast && fast->next){
fast=fast->next->next;
slow=slow->next;
if(slow==fast){
ListNode* index1=fast;
ListNode* index2=head;
while(index1!=index2){
index1=index1->next;
index2=index2->next;
}
return index1;
}
}
return nullptr;
}
};
解法2:哈希表
直观的思路就是把链表上的节点存到哈希表中记录下来,如果我们遇到了先前存过的节点,那就是遇到了环.
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode*> visited;
while(head){
if(visited.count(head))
return head;
visited.insert(head);
head=head->next;
}
return nullptr;
}
};
难度上来了,需要好好研究了。