24.两两交换链表中的节点
my solution
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* new_head = NULL;
ListNode* pre = NULL;
if (head != NULL){
while (head->next != NULL){
// swap two nodes
ListNode* next_node = head->next;
head->next = head->next->next; // 3 - NULL
next_node->next = head; // 4 - 3
// assigning new head node
if (!new_head){
new_head = next_node;
}
// assigning previous node
if (pre != NULL){ // pre = 1
pre->next = next_node; // 1 -4
}
pre = head; // pre = 3
// update node
head = head->next;
if (head == NULL){
cout << "break" << endl;
break;
}
}
}
return new_head == NULL ? head : new_head;
}
};
代码随想录
- 虚拟头结点确实方便
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;
}
};
19.删除链表的倒数第N个节点
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy_head = new ListNode(0);
if (n > 0){
dummy_head->next = head;
recursion_delete(dummy_head, n);
}
return dummy_head->next;
}
int recursion_delete(ListNode* head, int n){
if (head->next != NULL){
n = recursion_delete(head->next, n);
}
n--;
if (n == -1){
ListNode* temp = head->next;
head->next = head->next->next;
delete temp;
}
return n;
}
};
- 别人的5行递归
class Solution {
public:
int cur=0;
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(!head) return NULL;
head->next = removeNthFromEnd(head->next,n);
cur++;
if(n==cur) return head->next;
return head;
}
};
- 我之前也有想过,但是不想在函数外声明全局变量就没用
代码随想录的快慢指针
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* slow = dummyHead;
ListNode* fast = dummyHead;
while(n-- && fast != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
ListNode *tmp = slow->next; //C++释放内存的逻辑
slow->next = tmp->next;
delete tmp;
return dummyHead->next;
}
};
- 妙啊,倒数第n个删除,快慢指针循环,快指针先走n+1,这时候两个同步走,等快指针触底时,慢指针就指向倒数第n个节点了,直接删除就可以。
02.07 链表相交
- 要能宏观上理解题目,相交意味着两个list是有共同部分的。
- 我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:
- 此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。否则循环退出返回空指针。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0;
while (curA != NULL) { // 求链表A的长度
lenA++;
curA = curA->next;
}
while (curB != NULL) { // 求链表B的长度
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
swap (lenA, lenB);
swap (curA, curB);
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap--) {
curA = curA->next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != NULL) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
- 时间复杂度:O(n + m)
- 空间复杂度:O(1)
142. 环形链表
偷鸡
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
while(head) {
if(!less<ListNode *>()(head, head->next)) {
return head->next;
}
head = head->next;
}
return nullptr;
}
};
//o(n)算法,应该是最快的。 堆的地址从低到高,LeetCode的链表内存是顺序申请的,如果有环,head->next一定小于或等于head,哈哈哈哈哈
判断是否有环
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢
首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。 进入环了以后,相当于fast是以 单位1的速度一直靠近slow的,所以不会发生跳过去的情况
- x = 头结点到环形入口节点的距离
- y = fast 与 slow相遇节点 到 环形入口的距离
- z = 相遇节点到 入口的距离
- 一圈的长度 = z + y
那么相遇时: 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 != 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;
}
};