题目链接:24. 两两交换链表中的节点
class Solution
{
public:
ListNode *swapPairs(ListNode *head)
{
ListNode *dummy=new ListNode(0);
dummy->next=head;
ListNode *cur=dummy;
while(cur->next!=nullptr&&cur->next->next!=nullptr){
ListNode *tmp=cur->next->next;
ListNode *tmp1=cur->next->next->next;
tmp->next=cur->next;//第二个结点指向第一个结点
cur->next->next=tmp1;//第一个节点指向第三个节点
cur->next=tmp;//变换头节点指针
cur=cur->next->next;//cur往后移动2个节点
}
return dummy->next;
}
};
简单的一道题目,考察链表结点的交换位置,只要对链表的结点的位置有清楚的认知,就可以做出来。
题目链接:19. 删除链表的倒数第 N 个结点
class Solution {
public:
ListNode *removeNthFromEnd(ListNode *head, int n)
{
ListNode *dummy=new ListNode();
dummy->next=head;
ListNode *fast=dummy;//快指针
ListNode *slow=dummy;//慢指针
while(n--&&fast->next!=nullptr){
fast=fast->next;
}
while(fast->next!=nullptr){
fast=fast->next;
slow=slow->next;//slow并不是最终要指向被删除的节点,最终应该指向被删除节点的上一个节点
}
ListNode *tmp=slow->next;
slow->next=tmp->next;
return dummy->next;
}
};
所用的是快慢指针法,真的是一个很巧妙的方法!首先让快指针走n个结点,然后快、慢指针一起走,直到快指针到最后,这时候慢指针就已经指到了倒数第n个结点了。原理就是快指针先走了n,那么它离链表末端的距离就是size-n,然后快慢指针一起走size-n,也就是快指针指到末尾,慢指针指到倒数第n个结点。
我一开始想到的办法是先遍历一遍链表,计数一共有几个结点,再进行操作。这样子要多遍历一遍链表,时间复杂度高于快慢指针法,因为快慢指针法只要遍历一次结点。
题目链接:面试题 02.07. 链表相交
class Solution
{
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
int lenA=0;
int lenB=0;
ListNode *curA=headA;
ListNode *curB=headB;
while(curA!=nullptr){
curA=curA->next;
++lenA;
}
while(curB!=nullptr){
curB=curB->next;
++lenB;
}
curA=headA;
curB=headB;
if(lenB>lenA){
swap(lenA,lenB);
swap(curA,curB);
}
int gap=lenA-lenB;
while(gap--){
curA=curA->next;
}
while(curA!=nullptr){
if(curA==curB){
return curA;
}
curA=curA->next;
curB=curB->next;
}
return NULL;
}
};
这道题的关键在于理解题目,题目的意思其实是这两个链表,在某个结点之后,就是同一个链表了。类似于双头蛇。我们判断的条件不是cur->val相等,而是cur这个指针本身相等,即指向同一个结点。
class Solution {
public:
ListNode *detectCycle(ListNode *head)
{
unordered_set<ListNode *> visited;
ListNode *cur=head;
while(cur!=nullptr){
//find返回的是一个迭代器,如果找到就返回位置的迭代器,如果找不到就返回和end一样的迭代器
if(visited.find(cur)!=visited.end()){
return cur;
}
visited.insert(cur);
cur=cur->next;
}
return nullptr;
}
};
第一时间想到的应该是用集合来做,如果这个结点再次出现,那么就说明这是一个循环链表了,返回这个结点就行。时间复杂度o(n),遍历一个链表。空间复杂度o(n),创建一个set用于储存n个结点。
下面还有一个更巧妙的方法(令人惊叹,怎么相处这种办法的):
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)
空间复杂度:o(1),只用了fast,slow,index1,index2这几个指针
参考了代码随想录的解析:代码随想录解析
创建快、慢两个指针,快指针一次性走2个结点,慢指针一次性走1个节点。
如果链表没有环,那么两个指针永不相遇。
如果相遇,代表有环。
我们设从链表头节点到环的入口结点这段距离为x,从环的入口结点顺时针到两指针相遇结点的距离为y,两指针相遇结点顺时针到环的入口结点的距离为z。
这样子,环的长度为y+z
慢指针走过的距离是x+y
快指针走过的距离是x+y+n*(y+z)//走了n圈,即n*(y+z)
因为快指针一次性走2个结点。所以有2*(x+y)=x+y+n*(y+z)
可以化为x+y=n*(y+z)
我们要求的是环的入口,即x的长度
x=n*(y+z)-y=(n-1)*(y+z)+z
n必定大于1,因为快指针至少走1圈才会出现快慢指针相遇
假设这时候n==1
{
x==z
即头节点到环的入口的距离==快慢指针相遇结点顺时针到环的入口的距离
这时候我们使用两个指针,index1和index2
index1指向头节点
index2指向快慢指针相遇的结点
两指针同时移动,因为x==z,两指针最终相遇,相遇的点就是环的入口
}
如果n大于1,其实也就是快指针多走几圈,最终停下来的还是这个位置,也就是x会始终和z相等。
所以过程同上。