代码随想录训练营第四天| 24. 两两交换链表中的节点 ● 19.删除链表的倒数第N个节点 ● 面试题 02.07. 链表相交 ● 142.环形链表II

24. 两两交换链表中的节点

题目链接/文章讲解/视频讲解:  代码随想录
ListNode* swapPairs(ListNode* head) {
        if(head == nullptr || head->next == nullptr)
            return head;
        ListNode* cur = head->next;
        ListNode* newHead = cur;
        ListNode* pre = head;
        while (true) {
            if (cur->next != nullptr && cur->next->next != nullptr) {     //如果后续还有结点
                ListNode* temp = cur->next;
                cur->next = pre;
                pre->next = temp->next;
                pre = temp;
                cur = pre->next;
            }
            else{                                                         //如果后续没有节点
                if (cur->next != nullptr) {         //如果后续有一个不需要交换的单独结点
                    ListNode* temp = cur->next;
                    cur->next = pre;
                    pre->next = temp;
                }
                else {                              //如果后续没有节点了
                    cur->next = pre;
                    pre->next = nullptr;
                }
                break;
            }
        }
        return newHead;
    }

 思路:以上是我第一次写出的代码,使用pre和cur记录两个相邻的节点,每次将这两个节点后移;使用temp来记录cur之后的一个节点来更新pre和cur。具体的思路是先将cur的next指向pre,再将pre的next指向原本cur后面两个的节点,这样后面两个节点交换后顺序还是正确的。还有就是通过判断后续是否还有节点来确定交换的逻辑,若后面只剩一个节点了,则就将cur的next指向pre后再将pre的next指向这个后续节点即可;若后面没有节点了,则就将cur的next指向pre后再将pre的next指向这个空指针,这样就可以完成两两交换了。

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

看了视频讲解和示例代码,发现我想的复杂且绕弯了,其实只需要使用要翻转的两个节点的前一个节点,就可以实现两两交换了,也可以直接吧pre的next指向原本cur后面的一个节点,这样做思路更加清晰简单,同时也不需要进行最后的边界特例的交换,代码更加简洁明了。

 19.删除链表的倒数第N个节点  

题目链接/文章讲解/视频讲解:代码随想录

ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* fast = dummyHead;
        ListNode* slow = 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 nth;
        
        return dummyHead->next;
    }

 示例代码,重点感觉是需要使用虚拟指针,能避免对头结点的讨论, 思路是使用快慢指针,用快指针提前走n+1步,这样快慢指针再同时前进时当快指针指向末尾的空指针时,满指针正好停在倒数第n个节点的前一个节点,就可以直接删除倒数第n个节点了。

面试题 02.07. 链表相交  

题目链接/文章讲解: 代码随想录
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
       int lenA = 0, lenB = 0;
       ListNode* pa = headA;
       ListNode* pb = headB;
       while(pa || pb){
           if(pa){
               ++lenA;
               pa = pa->next;
           }
           if(pb){
               ++lenB;
               pb = pb->next;
           }
       }
       pa = headA;
       pb = headB;
       while(lenA > lenB){
           pa = pa->next;
           --lenA;
       }
       while(lenA < lenB){
           pb = pb->next;
           --lenB;
       }
       while(pa && pb){
           if(pa == pb) return pa;
           pa = pa->next;
           pb = pb->next;
       }
       return nullptr;
   }

 思路:先计算链表A和B的长度,再将长的链表走到距结尾和短的距离相同的位置,然后让AB同时前进,若遇到相同的节点了,则说明有重合的部分,返回相同节点即可,若不存在,则返回空指针。

 142.环形链表II  

题目链接/文章讲解/视频讲解:代码随想录

 快慢指针解决 在第141题,我们提到过快慢指针,先判断是否有环,如果有环,在来找环的入口。我们假设是有环的,那么会有两种情况,我们来画个图看一下

1,环很大

假如他们在相遇点相遇了,那么慢指针走过的距离是a+b,快指针走过的距离就是a+b+c+b,因为相同时间内快指针走的距离是慢指针的2倍,所以有a+b+c+b = 2*(a+b),整理得到a=c,也就是说从相遇点到环的入口和链表的起始点到环的入口,距离是一样的。在相遇点的时候我们可以使用两个指针,一个从相遇点开始,一个从链表头开始,他们每次都走一步,直到他们再次相遇位置,那么这个相遇点就是环的入口。

代码随想录训练营第四天| 24. 两两交换链表中的节点 ● 19.删除链表的倒数第N个节点 ● 面试题 02.07. 链表相交 ● 142.环形链表II_第1张图片

2,环很小

那么这种情况,快指针在环上转了好几圈了,慢指针才走到环上,假如快指针在环上已经走了m圈了,慢指针在环上走了n圈,他们最终在环上相遇

代码随想录训练营第四天| 24. 两两交换链表中的节点 ● 19.删除链表的倒数第N个节点 ● 面试题 02.07. 链表相交 ● 142.环形链表II_第2张图片

那么慢指针走过的距离是:a+b+n(b+c) (b+c其实就是环的长度) 快指针走过的距离是:a+b+m(b+c) 在相同的时间内快指针走过的距离是慢指针的2倍,所以有

a+b+m(b+c) = 2(a+b+n*(b+c)) 整理得到

a+b=(m-2n)(b+c), 上面b+c其实是环的长度,也就是说a+b等于(m-2n)个环的长度,这个时候我们还可以使用两个指针一个从相遇点开始,一个从链表头开始,这时候就会出现一个现象就是一个指针在链表上走,一个指针在环上转圈,最终会走到第1种情况,就是环很小(我们可以认为链表前面减去m-2n-1个环的长度就是第一种情况了)

搞懂了上面的分析过程,我们来看下代码

ListNode *detectCycle(ListNode *head) {
        if (head == NULL)
            return NULL;
        ListNode *slow = head;
        ListNode *fast = head;
        while (fast -> next != NULL && fast -> next -> next != NULL) {
            fast = fast -> next -> next;
            slow = slow -> next;
            if (fast == slow) break;
        }
        //判断是否有环 
        if(fast->next==NULL||fast->next->next==NULL)return NULL;
        //有环则将fast移动至head并移动S2距离
        fast=head;
        while(fast!=slow){
            slow=slow->next;
            fast=fast->next;
        }
       return fast;
    }

你可能感兴趣的:(链表,java,面试)