24. 两两交换链表中的节点
思路和翻转链表那个像,也是搞几个指针,按着链表的顺序走一遍就行,就是【1】写核心逻辑需不需要tmp防止覆盖,【2】哪个指针指着哪个容易晕。
AC代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head)
{
if(head == nullptr || head->next == nullptr)return head; // 只有0或者1个元素就返回
ListNode* dummynode = new ListNode(0,head);
ListNode* last = dummynode;
ListNode* now = dummynode->next;
while(now != nullptr && now->next != nullptr)
{
cout << last->val << endl;
cout << now->val << endl;
// 交换
last->next = now->next;
ListNode* tmp = now->next;
now->next = now->next->next;
tmp->next = now;
//前进
last = now;
now = now->next;
}
return dummynode->next;
}
};
19. 删除链表的倒数第 N 个结点
我一开始AC的做法,先遍历整个链表计算一共几个元素,然后计算要删除的是正着数第几个节点,再遍历走到要删除的节点的前一个节点,删除。【遍历两遍】
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
// 先扫描一次看一共几个节点
ListNode* dummyhead = new ListNode(0,head); // 加一个虚拟头节点
int sum = 0; // 一共k个节点
ListNode* cur = dummyhead;
while(cur->next != nullptr)
{
sum++;
cur = cur->next;
}
int k = sum - n ; //到要删除节点的前一个
cur = dummyhead;
while(k--)
{
cur = cur->next;
}
cur->next = cur->next->next;
return dummyhead->next;
}
};
看了讲解发现用《同向快慢双指针》可以优化到一遍扫描+上面这个又忘记delete要删除的元素,优化之后的AC代码:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
ListNode* dummyhead = new ListNode(0,head); // 加一个虚拟头节点
ListNode* fast = dummyhead;
ListNode* slow = dummyhead;
n++;
while(n--)fast = fast->next; //快指针先走n+1步
while(fast != nullptr)
{
fast = fast->next;
slow = slow->next;
}
ListNode* tmp = slow->next;
slow->next = slow->next->next;
delete tmp;
return dummyhead->next;
}
};
面试题 02.07. 链表相交
一开始没有想法,只想到了O(m*n)的暴力解法。后来看了提示,才想到可以从两个链表后面长度相同的部分开始往后移。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
ListNode* cura = headA;
ListNode* curb = headB;
// 计算两个链表的长度
int numa = 0;
int numb = 0;
while(cura != nullptr)
{
cura = cura->next;
numa++;
}
while(curb != nullptr)
{
curb = curb->next;
numb++;
}
cura = headA;
curb = headB;
// 把两个链表当前指针同步到后面的长度一致
if(numa < numb) // b比较长
{
int k = numb - numa;
while(k--){curb = curb->next;}
}
else if(numa > numb) //a比较长
{
int k = numa - numb;
while(k--){cura = cura->next;}
}
while(cura != nullptr) // 后移 找交点
{
if(cura == curb)return cura;
cura = cura->next;
curb = curb->next;
}
return nullptr;
}
};
142. 环形链表 II
我的AC写法超暴力写法,又占时间,又占空间的。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head)
{
// 我的暴力想法 来一个vector 存着过去扫过的节点地址
vector a;
ListNode * cur = head;
while(cur != nullptr)
{
auto it = find(a.begin(),a.end(),cur);
if(it == a.end()) // 在a中没有
a.push_back(cur);
else return cur;
cur = cur->next;
}
return nullptr;
}
};
学习一下我想不到的优化写法。学完感慨,妙啊!!!下面讲一下注意点:
Q1:判断有无环。==》 快慢指针,快的每次走2步,慢的每次走1步,如果存在环的话,快慢指针一定会在环里面相遇!证明:【1】首先,快的比慢的走的快,所以不可能在直线上相遇,要是相遇只可能在环里面。【2】其次,想到在一个操场跑步,当慢的进入环,早就在环里的快的 就相当于要追慢的,而快的速度比慢的快,所以一定可以追上!
Q1.1 为什么快的要2步,不能3步,4步?==》 因为快的2步时,就是相对慢的每次增加一步的节奏逼近,如果快的走3步时,则快的相对慢的以每次2步的增速逼近,由于走的是离散的,也就是存在慢的就在自己前面一步,但是快的相对慢的迈了两步,相遇超过了但错过了没发现。
Q2:现在如果判断有环了,怎么找环入口?==》
那么相遇时: slow指针走过的节点数为: x + y
, fast指针走过的节点数:x + y + n (y + z)
,其中:n为fast指针在环内走了n圈才遇到slow指针。因为fast指针是一步走两个节点,slow指针一步走一个节点, 易得:
(x + y) * 2 = x + y + n (y + z)
化简得 : x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。所以要求x ,将x单独放在左面:
x = n (y + z) - y
消掉 -y 得:
x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明:先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
假设 n为1的时候,公式就化解为
x = z
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
Q2.1: 为什么那么相遇时: slow指针走过的节点数为: x + y? 不是x + y + k (y + z).
==》因为当慢的进入环的时候,最差的情况,快的就在自己前面一格,所以慢的转小于一圈的时间内,快的一定可以追到自己。
Q2.2:n的解释:
n是一个不知道多少的定值,可能是1,可能是其他正整数,但不论是哪个定值,按下面的t1 != t2 为条件循环都满足x = (n - 1) (y + z) + z
,也不在乎n是多少了。
class Solution {
public:
ListNode *detectCycle(ListNode *head)
{
//
ListNode * fast = head;
ListNode * slow = head;
while(fast != nullptr && fast->next != nullptr)
{
fast = fast->next->next; //快指针走两格
slow = slow->next; //慢指针走一格
if(fast == slow) //相遇了说明有环
{
ListNode * t1 = head;
ListNode * t2 = fast;
while(t1 != t2) // 找到环就找环入口
{
t1 = t1->next;
t2 = t2->next;
}
return t1;
}
}
return nullptr; //没有环
}
};
果然!时间空间都小了,时间快了12倍!
1. 这次学到了快慢指针的更多应用
2. 感受了一个环形链表比较难的题,正解很难想,但很有意思
3. day6才在补day4 的wuwu , 累计用时3个半小时吧