代码随想录-Day4| 24.移除链表元素,19.链表设计,142.反转链表

120 阅读6分钟

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的,所以不会发生跳过去的情况

环形指针.png

  • 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;
     }
 };