代码随想录
这个题似乎不难,但是做的时候还是花了一些时间,把自己的思路记录一下。先给出代码,再解释一下:
/**
* 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;
ListNode* pHead = head->next;
ListNode *cur1,*cur2;
//初始化
ListNode* tmp = head;
while(tmp != nullptr && tmp->next != nullptr)
{
cur1 = tmp;
cur2 = tmp->next;
tmp = cur2->next;
//交换
cur2->next = cur1;
cur1->next = tmp;
if(tmp != nullptr && tmp->next != nullptr)
cur1->next = tmp->next;
}
return pHead;
}
};
这里需要用到三个指针,两个指针cur1和cur2指向要交换的两个节点,tmp指向下一个待交换的两个节点中的第一个节点,这也是单链表的特点,当要改变某一个节点的指向的时候,要先记录一下该节点的下一个节点。
if(head == nullptr || head->next == nullptr) return head;
ListNode* pHead = head->next;
ListNode *cur1,*cur2;
//初始化
ListNode* tmp = head;
cur1 = tmp;
cur2 = tmp->next;
tmp = cur2->next;
//交换
cur2->next = cur1;
cur1->next = tmp;
if(tmp != nullptr && tmp->next != nullptr)
cur1->next = tmp->next;
这样就可以循环了,当tmp节点为空或者下一个节点为空,那么不再继续交换,所以循环的条件是tmp != nullptr && tmp->next != nullptr
。
代码随想录给出的代码如下:
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;
}
};
看了一下,似乎没有特别之处,没有用什么特别的方法,相比之下感觉自己的代码更好理解哈哈。
这个题说难不难,说简单也不简单,做的时候一定要画图理解,涉及到的指针较多,不画图很容易出错。在改变链表中某一个节点的下一个节点时,一定要先保存原来的节点。
这个题难点似乎是在倒数上,如果是正数那就是链表的遍历,找到删除的节点,然后删除即可。既然会删除正数的第N个点,那就把倒数转化为正数,问题就解决了。具体做法是先遍历一边链表,得到链表的长度,然后简单相减就可以得到正数的第几个点了。整体来说这个题不难,写完代码也是一次就过,还是挺开心的哈。代码如下:
/**
* 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* cur = head;
int size = 0;
//遍历链表,获取长度
while(cur != nullptr)
{
cur = cur->next;
size++;
}
//把倒数转化为正数,注意题目中的倒数从1开始,这里的正数从0开始
int index = size - n;
//添加虚拟头节点,方便操作
ListNode* dummyNode = new ListNode(0,head);
//找到要删除的节点的上一个节点
cur = dummyNode;
while(index--) cur = cur->next;
//删除节点
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
head = dummyNode->next;
delete dummyNode;
return head;
}
};
虽然上面自己的方法也能一次通过,但是必须遍历两次。代码思想录给出的方法使用了双指针法。具体做法是让fast指针向移动 n步,然后fast指针和slow指针再一起移动,当fast指针到最后的时候slow指针就指向了要删除节点的上一个节点(之前也一直在强调,删除某一个节点,指针应该指向该节点的上一个节点)。这个实现思路还是非常有启发性的,看了思路之前自己再写一遍:
/**
* 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* dummyNode = new ListNode(0,head);
ListNode* fast = dummyNode;
ListNode* slow = dummyNode;
int i = n ;
while(i--) fast = fast->next;
while(fast->next != nullptr)
{
fast = fast->next;
slow = slow->next;
}
ListNode* tmp = slow->next;
slow->next = slow->next->next;
delete tmp;
return dummyNode->next;
}
};
本题不用双指针法也能做出来,但是双指针法确实要更优一些,提供另外一种定位倒数节点的方法,只需要遍历一遍就可以了。
这个题也不难,又是一把过哈哈哈,终于直到为啥今天四个题,原来都是简单题。看题目中给出的图,相交部分都是从后面开始的(也只能是这样,如果前面相交再分开,那么最后一个交点指向两个节点?显然不行),而单链表只能从前往后遍历。这题思路还是挺多的,这里说一下我想到的一些方法:
这里补充一个点,一开始以为比较两个节点的值就可以判断是不是同一个节点,但是这种方法是错的,不同的节点也可以有相同的值,所以正确的是比较地址。
上面三种比较起来,第三种比较容易,代码实现如下:
/**
* 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) {
if(headA == nullptr || headB == nullptr) return nullptr;
stack<ListNode*> stack1,stack2;
ListNode* cur1 = headA;
ListNode* cur2 = headB;
while(cur1 != nullptr)
{
stack1.push(cur1);
cur1 = cur1->next;
}
while(cur2 != nullptr)
{
stack2.push(cur2);
cur2 = cur2->next;
}
ListNode* p = nullptr; //指向第一个相交的节点
while(!stack1.empty() && !stack2.empty() && stack1.top() == stack2.top())
{
p = stack1.top(); // 或者 p = stack2.top();
stack1.pop();
stack2.pop();
}
return p;
}
};
代码随想录里给出的思路是将两个链表“右对齐”,然后两个指针分别从较短的链表头节点处开始遍历,如下图所示:
清楚了这个思路之后,代码实现就简单了,代码实现如下:
/**
* 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) {
if(headA == nullptr || headB == nullptr) return nullptr;
int size_A = 0,size_B = 0;
//遍历链表A,得到其长度
ListNode* cur_A = headA;
while(cur_A != nullptr)
{
cur_A = cur_A->next;
size_A++;
}
//遍历链表B,得到其长度
ListNode* cur_B = headB;
while(cur_B != nullptr)
{
cur_B = cur_B->next;
size_B++;
}
//计算长度差
int delta = abs(size_A - size_B);
//对齐
cur_A = headA;
cur_B = headB;
if(size_A > size_B)
while(delta--) cur_A = cur_A->next;
else
while(delta--) cur_B = cur_B->next;
//找交点
ListNode* p = nullptr;
while(cur_A != cur_B)
{
cur_A = cur_A->next;
cur_B = cur_B->next;
}
if(cur_A == cur_B) p = cur_A;
return p;
}
};
代码随想录的代码就不在这里给出了,具体可点击这里查看。
这个题不难,自己想到的方法也可以实现,但是空间复杂度是O(n),时间复杂度还可以。代码随想录给出的方法代码看着繁琐,但是清楚思路之后就很简单了,空间复杂度为O(1)。
这个题之前做过,还有一些想象,难点在于数学推动,代码随想录给出的推倒不是很理解,这里按照自己的想法推倒一下。
首先判断是否有环,用两个指针fast和slow,两个指针都从头节点开始往后移动,fast指针每次移动两个节点,slow指针每次移动1个节点(理论上只要两个指针的移动速度不一样就可以了),这样如果有环的话这两个指针一定会在环内相遇,所以有环无环的判断还是挺简单的,难点在于入环节点,推倒如下:
假设A为头节点,AB = a , 环的长度为b,设fast指针的移动速度v1 = 2,slow指针的移动速度v2 = 1。当slow指针到达B点时移动了距离a,此时fast指针移动了2a,减去AB段的距离,fast指针在环内移动了距离a,两个和指针在环内相遇u时满足下面的公式:
(a+2t) - t = mb , m = 1,2,3,..
其中m表示fast指针和slow指针第几次重合。(这个问题就是高中物理里的追击相遇问题)
取 m = 1,化简上面的公式:
t = b - a
上面的时间两个指针第一次重合的时间(从slow指针在B点开始移动时开始计时),因此可以得到两个指针第一次重合的位置距离B点顺时针b-a位置处(图中的C点),这样从C点顺时针到B点的距离为a,这个距离和A点到B点的距离相等,这样值只要让两个指针分别从A点和C点出发,每次移动一个节点,两个指针相遇的位置就是环的入口节点。推倒出这个结论之后代码实现就很简单了。代码如下:
/**
* 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) {
if(head == nullptr || head->next == nullptr) return nullptr;
ListNode* slow = head->next;
ListNode* fast = head->next->next;
while(fast != nullptr && fast->next != nullptr && slow != fast)
{
slow = slow->next; //每次移动一个节点
fast = fast->next->next; //每次移动两个节点
}
if(fast == nullptr || fast->next == nullptr) return nullptr;
//找环的入口
slow = head;
while(slow != fast)
{
slow = slow->next; //每次移动一个节点
fast = fast->next; //每次移动一个节点
}
return slow;
}
};
在写的时候遇到一些问题,这里记录一下:
ListNode* slow = head->next;
ListNode* fast = head->next->next;
初始化的时候两个指针就要移动了,不能都指向head,否则在第一个while循环处就直接跳过了。
上面的方法其实就是因为之前看过代码随想录的方法,在求解思路上一致,在数学推倒上不太明白代码随想录的推倒,所以自己推倒了一下,最终的结论是一样的。代码随想录给出的代码如下:
/**
* 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) {
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;
}
};
本题的代码实现不难,难点在于数学推倒,即怎么判断环的入口,只要能推倒出结论,代码实现就很简单了。
今天有四个题,相对来说都不是很难。对链表的操作有时候涉及到很多指针,画图可以帮助理解链表的变化过程,有时候甚至需要先画图来确定处理过程,然后再转化为代码,两两交换那题就是如此,所以说在对链表的操作过程中画图是非常重要的。今天的题到此结束,明天终于可以休息一天了。照 这样的进度到后面不熟悉的部分肯定要跟不上了。