LeetCode24. 两两交换链表中的节点
思路:
初始时,cur指向虚拟头结点,然后进行如下三步:
操作之后,链表如下:
即
C++代码如下:
/**
* 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)
{
ListNode* dummyHead = new ListNode(0); //设置虚拟头结点
dummyHead->next = head;
ListNode* cur = dummyHead;
while((cur->next!=NULL) && (cur->next->next!=NULL))
{
ListNode* tmp0 = cur->next; //记录临时节点
ListNode* tmp1 = cur->next->next->next; //记录临时节点
cur->next = cur->next->next; //步骤一
cur->next->next = tmp0; //步骤二
cur->next->next->next = tmp1; //步骤三
cur = cur->next->next; //更新cur,准备下一轮交换
}
return dummyHead->next;
}
};
总结:一定要画图,若不画图,操作多个指针很容易乱,注意操作的先后顺序。
LeetCode19. 删除链表的倒数第N个结点
思路:看到这题的第一个想法就是:先把整个链表反转,找出想要删除的结点,再反转回来。
C++代码如下(正确):
/**
* 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);
dummyHead->next = head;
ListNode* pre = NULL;
ListNode* cur = head;
while(cur != NULL)
{
ListNode* tmp = cur->next; //暂存
cur->next = pre; //翻转
pre = cur; //更新
cur = tmp;
}
//return pre; //此时pre就是原链表的最后一个结点,翻转后链表的头结点
ListNode* dummyHead02 = new ListNode(0);
dummyHead02->next = pre; //虚拟头结点指向翻转后链表的头结点
ListNode* cur02 = dummyHead02;
n = n-1;
while(n--)
{
cur02 = cur02->next;
}
ListNode* deleteNode = cur02->next;
cur02->next = cur02->next->next;
delete deleteNode;
//return dummyHead02->next; //此时返回的是翻转后且删除结点后的链表的头结点
ListNode* pre03 = NULL;
ListNode* cur03 = dummyHead02->next;
while(cur03 != NULL)
{
ListNode* tmp03 = cur03->next;
cur03->next = pre03;
pre03 = cur03;
cur03 = tmp03;
}
return pre03;
}
};
思考:用双指针法,非常巧妙!总体思路如下(双指针的经典应用):如果要删除倒数第n个节点,让fast先移动n步,然后让fast和slow同时移动,直到fast指向链表末尾,此时删掉slow所指向的节点即可。
C++代码如下:
/**
* 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); //设置一个虚拟头结点
dummyHead->next = head;
ListNode* slowIndex = dummyHead;
ListNode* fastIndex = dummyHead;
n = n+1;
while(n--)
{
fastIndex = fastIndex->next;
}
while(fastIndex != NULL)
{
slowIndex = slowIndex->next;
fastIndex = fastIndex->next;
}
//此时slowIndex->next就是要删除的节点
slowIndex->next = slowIndex->next->next;
return dummyHead->next;
}
};
面试题 02.07. 链表相交
思路:
- 分别求出两个链表的长度;
- 求出长度差gap;
- 将较长链表的辅助节点向后移动gap长度,目的是为了末尾位置对齐,让curA与curB在同一起点上;
- 遍历curA和curB,遇到相同的则直接返回。
代码如下:
/**
* 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 = new ListNode(0);
curA->next = headA;
int lenA = 0;
while(curA->next != NULL)
{
lenA++;
curA = curA->next;
}
//此时lenA为A链表的长度
ListNode* curB = new ListNode(0);
curB->next = headB;
int lenB = 0;
while(curB->next != NULL)
{
lenB++;
curB = curB->next;
}
//此时lenB为B链表的长度
curA = headA; //重置一下
curB = headB;
//求出长度差gap
int gap = 0;
if(lenA >= lenB)
{
gap = lenA - lenB;
}
else
{
gap = lenB - lenA;
}
//把curA向后移动,让curA和curB在同一起点上(末尾位置对齐)
if(lenA < lenB) //目的是为了让长的为lenA,headA
{
swap(curA,curB);
swap(headA,headB);
}
while(gap--)
{
curA = curA->next;
}
//此时curA到链表尾部的距离和curB到链表尾部的距离相同
//遍历curA和curB,遇到相同则直接返回
while((curA != NULL)&&(curA != curB))
{
curA = curA->next;
curB = curB->next;
}
return curA;
}
};
LeetCode142. 环行链表II
思路:
首先,要判断链表是否有环:定义快慢指针,快指针fastIndex每次走2个节点,慢指针slowIndex每次走1个节点,如果fastIndex和slowIndex指针在途中相遇 ,说明这个链表有环。fastIndex走得比较快,如果能和slowIndex相遇,说明一定是在环内相遇的;至于为什么一定可以相遇,因为fastIndex每次走两步,slowIndex每次走一步,相对于slowIndex而言,fastIndex每次一个节点一个节点的靠近slowIndex,所以一定可以相遇。
接下来,有环的话,如何找到这个环的入口:假设从头结点到环形入口节点数为x,环形入口节点到 fastIndex指针与slowIndex指针相遇节点的节点数为y,从相遇节点再到环形入口节点节点数为z。
那么相遇时: slowIndex指针走过的节点数为: x + y, fastIndex指针走过的节点数:x + y + n (y + z),n为fastIndex指针在环内走了n圈才遇到slowIndex指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:(x + y) * 2 = x + y + n (y + z)。
公式经过移项,有x = n (y + z) - y,即x = (n - 1) (y + z) + z,注意这里n一定是大于等于1的,因为 fastIndex指针至少要多走一圈才能相遇slowIndex指针。
先拿n等于1举例子,n=1时候,公式就是x = z,也就是说,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
n大于1时候,也就是fastIndex指针在环形转了n圈之后才遇到slowIndex指针。但是这种情况和n为1时候的效果是一样的,同样可以通过这个方法找到环形的入口节点,只不过,从相遇节点出发的指针在环里多转了( n-1)圈,然后再遇到从头结点出发的指针,相遇点依然是环形的入口节点。
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)
{
//定义快慢指针,fastIndex每次走2个节点,slowIndex每次走1个节点
ListNode* fastIndex = head;
ListNode* slowIndex = head;
//这样子它们一定会相遇的,因为相对于slowIndex来说,fastIndex每次一个节点一个节点的靠近slowIndex。
while((fastIndex != NULL)&&(fastIndex->next != NULL))
{
fastIndex = fastIndex->next->next; //移动2个节点
slowIndex = slowIndex->next; //移动1个节点
if(fastIndex == slowIndex) //两个指针相遇了
{
//再定义两个指针,从头结点出发一个指针,从相遇节点出发一个指针,每次都只走1个节点,那么它们相遇的节点就是入环的第一个节点。
ListNode* cur1 = fastIndex; //从相遇节点出发
ListNode* cur2 = head; //从头结点出发
while(cur1 != cur2)
{
cur1 = cur1->next;
cur2 = cur2->next;
}
return cur1;
}
}
return NULL;
}
};
注意:
在定义快慢指针的时候,是定义成:
ListNode* fastIndex = head;
ListNode* slowIndex = head;
而不是定义成:
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* fastIndex = dummyHead;
ListNode* slowIndex = dummyHead;
因为快指针与慢指针都要一起从头结点出发,而不是从虚拟节点出发,否则结果(相遇的节点)就会出错,以致于一直找不到入环节点就会一直循环最后超时。