代码随想录算法训练营第四天 | 24. 两两交换链表中的节点、707.设计链表、206.反转链表、142.环形链表II
24. 两两交换链表中的节点
思路
-
使用正常思路即可,记得用虚拟头节点可以让算法更简单,思路如下:
/**
* 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 != nullptr && cur->next->next != nullptr) {
ListNode *tmp1 = cur->next;
ListNode *tmp2 = cur->next->next->next;
//步骤一
cur->next = cur->next->next;
//步骤二
cur->next->next = tmp1;
//步骤三,这一步是指向下一组的虚拟头节点
cur->next->next->next = tmp2;
//cur后移两位
cur = cur->next->next;
}
ListNode *result = dummyHead->next;
delete dummyHead;
return result;
}
};
看完代码随想录之后的想法
递归版本
-
使用虚拟头节点再删除,这样就不用把头节点分开判断了
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ //递归版本 struct ListNode* swapPairs(struct ListNode* head){ //递归结束条件:头节点不存在或头节点的下一个节点不存在。此时不需要交换,直接返回head if(!head || !head->next) return head; //创建一个节点指针类型保存头结点下一个节点 struct ListNode *newHead = head->next; //更改头结点+2位节点后的值,并将头结点的next指针指向这个更改过的list head->next = swapPairs(newHead->next); //将新的头结点的next指针指向老的头节点 newHead->next = head; return newHead; }
迭代版本
//迭代版本
struct ListNode* swapPairs(struct ListNode* head){
//使用双指针避免使用中间变量
typedef struct ListNode ListNode;
ListNode *fakehead = (ListNode *)malloc(sizeof(ListNode));
fakehead->next = head;
ListNode* right = fakehead->next;
ListNode* left = fakehead;
while(left && right && right->next ){
left->next = right->next;
right->next = left->next->next;
left->next->next = right;
left = right;
right = left->next;
}
return fakehead->next;
}
19.删除链表的倒数第N个节点
思路
- 要求使用一趟扫描实现,没思路,难道不是必须完全走一遍才行
看完代码随想录之后的想法
双指针法
-
又是双指针,先让fast走n个节点,然后slow和fast一起走,当fast到末尾时,slow就是倒数第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 *dummyHead = new ListNode(0); dummyHead->next = head; ListNode *fast = dummyHead; ListNode *slow = dummyHead; for(int i = 0; i < n; 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 dummyHead->next; } };
160.链表相交
思路
- 盲猜快慢指针,双指针
- 具体怎么做不知道
看完代码随想录之后的想法
双指针法
-
让末尾对其即可,找二者第一次相等的值,返回交点(阿哲)
/** * 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 lenA = 0, lenB = 0; //计算各自链表的长度 while(curA != nullptr) { lenA++; curA = curA->next; } while(curB != nullptr) { lenB++; curB = curB->next; } //把较长链表指针后移,或者直接让A作为最长链表 //注意赋值要先赋哦 curA = headA; curB = headB; if (lenB > lenA) { swap(lenA, lenB); swap(curA, curB); } int gap = lenA - lenB; while(gap--) { curA = curA->next; } //注意如果没有相交要返回null while(curA != nullptr) { //注意题目,题目中是对象相等,而不是值相等 if (curA == curB) { return curA; } curA = curA->next; curB = curB->next; } return NULL; } };
142.环形链表II
思路
-
快慢指针,一个指针是另一个指针速度的两倍,如果二者相遇,则有环,如果二者不相遇,则无环,结束条件是有指针为null
-
但是这样无法找到入环的第一个节点,怎么办?以下代码并不能找到入口
/** * 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->next != nullptr && slow->next != nullptr && fast->next->next != nullptr ) { if(fast == slow) { return } fast = fast->next->next; slow = slow->next; } return NULL; } };
看完代码随想录之后的想法
相遇节点法
- 假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z
-
那么相遇时: 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同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
-
快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
/** * 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; //slow不用判断,一定是fast先到 while(fast != nullptr && fast->next != nullptr ) { fast = fast->next->next; slow = slow->next; //快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇 if(fast == slow) { ListNode *ind1 = fast; ListNode *ind2 = head; while(ind1 != ind2) { ind1 = ind1->next; ind2 = ind2->next; } return ind2; } } return NULL; } };
总结
- 使用 虚拟头节点, 链表的增删改会方便很多
遇到困难
- 对while循环和链表的指针的思考还不够细致
- 有一些没必要的判断,总是根据经验去判断
- 需要对递归法有新的理解
今日收获
- 今日打卡晚了两天,以后算法题要加紧速度