代码随想录算法训练营29期Day4|LeetCode 24,19,面试题02.07,142

  文档讲解:代码随想录

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

题目链接:https://leetcode.cn/problems/swap-nodes-in-pairs/

思路:

       本题目与LeetCode206题反转链表有异曲同工之秒。

        在处理两个节点的反转时,分析易知,我们只需要考虑这两个节点本身以及它们前后两个节点共四个节点即可。

        假设我们当前处理到了这样一段:p->q->r->w。我们如何进行反转呢?很简单,先处理好前后的关系,即让p->r,q->w,然后进行反转即可:r->q。

        用代码表示则是:q=p->next,r=q->next,p->next=r,q->next=r->next,r->next=q。p在链表上遍历即可,每次向下移动两位。

核心代码:

/**
 * 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* p;
        ListNode* q;
        ListNode* r;
        ListNode* dhead=new ListNode(0);
        dhead->next=head;
        p=dhead;
        while(p!=NULL){
            if(p->next!=NULL) q=p->next;
            else break;
            if(q->next!=NULL) r=q->next;
            else break;
            p->next=r;
            q->next=r->next;
            r->next=q;
            p=q;
        }
        return dhead->next;
    }
};

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

题目链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/

思路:

       本题目非常简单,我们遍历到每个节点之时保证另一个指针同时遍历到从这个节点开始的倒数第N个节点即可。

        具体如何实现呢?首先我们设一个虚拟头结点dhead,以此为起点遍历N次,遍历到的节点记为cur。则dhead就是以cur为终点的倒数第N个点。接下来dhead和cur同时向下遍历,当cur为链表的终点时,dhead遍历到的点就是终点的倒数第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* dhead=new ListNode();
        dhead->next=head;
        ListNode* cur;
        cur=dhead;
        while(n--) cur=cur->next;
        ListNode* tmp;
        tmp=dhead;
        while(cur->next!=NULL){
            cur=cur->next;
            tmp=tmp->next;
        }
        tmp->next=tmp->next->next;
        head=dhead->next;
        return head;
    }
};

面试题02.07.链表相交

题目链接:https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/

思路:

       本题目要求两个单链表相交的起始节点。

       首先我们要意识到一个非常关键的点,就是两个链表相交的部分长度一致,因为是同一段。同时我们知道,两个链表长度不同,因此较长的那个链表的前一部分必不可能与短的链表相交。因此我们可以先对较长的链表进行遍历,找到一个节点,该节点满足长链表删除该节点之前的部分后与短链表长度相同。

        然后长链表从找到的节点开始遍历,短链表从自身起点开始遍历,每次判断遍历得到的两个点是否相同,相同则找到了相交起点,不同则继续遍历即可。

核心代码:

/**
 * 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* dheadA=new ListNode(0);
        ListNode* dheadB=new ListNode(0);
        int cntA,cntB;
        dheadA->next=headA;dheadB->next=headB;
        cntA=cntB=0;
        ListNode* curA;
        ListNode* curB;
        curA=dheadA;curB=dheadB;
        while(curA->next!=NULL){
            curA=curA->next;
            cntA++;
        }
        while(curB->next!=NULL){
            curB=curB->next;
            cntB++;
        }
        curA=dheadA;curB=dheadB;
        if(cntA>=cntB){
            int n=cntA-cntB;
            while(n--) curA=curA->next;
        }
        else{
            int n=cntB-cntA;
            while(n--) curB=curB->next;
        }
        while(curA->next!=NULL&&curB->next!=NULL){
            curA=curA->next;
            curB=curB->next;
            if(curA==curB) break;
        }
        if(curA==curB) return curA;
        else return NULL;
    }
};

142.环形链表II

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

思路:

       本题目我有多个思路,能实现的目前只有两个,下面进行介绍。

       第一个思路非常取巧,这也是我代码中展示的思路。我们开一个set数组记录链表中的节点是否被遍历过。每次遍历到一个节点就进行判断,如果没被遍历过则进行记录,如果被遍历过了证明当前节点就是入环节点。同时如果遍历过程中发现下一个节点为空,则证明无环。

        第二个思路则是双指针,就是快慢指针法。我们开两个指针,一快一慢,记为fast和slow,两个指针均从head出发,fast每次向下遍历两个节点,slow每次遍历一个节点。我们容易知道,如果存在环的话,slow和fast必定会再次相遇。

        首先无环的情况非常好判断,如果fast越过链表末端,即会访问到NULL节点,证明该链表无环。

        下面分析有环的情况,以及相遇过程中fast和slow的遍历过程:

        设链表共有a+b个节点,其中链表头部到链表入口有a个节点(不计链表入口节点),链表环有b个节点,设两指针分别走了f步和s步,则有:

       1.fast走的步数是slow步数的2倍,即 f=2s(fast每次走2步,slow走1步)。

       2.fast 比slow多走了n个环的长度,即 f=s+nb( 解析:双指针都走过 a 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走环的长度整数倍 )。

       将以上两式相减得到 f=2nb,s=nb,即 fast 和 slow 指针分别走了 2n,n 个环的周长。

        同时易知,如果让指针从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数是:k=a+nb,即先走 a 步到入口节点,之后每绕 1 圈环( b 步)都会再次到入口节点。而目前 slow 指针走了 nb 步。因此,我们只要想办法让 slow 再走 a 步停下来,就可以到环的入口。

        但是我们不知道 a 的值,该怎么办?依然是使用双指针法。考虑构建一个指针,此指针需要有以下性质:此指针和 slow 一起向前走 a 步后,两者在入口节点重合。那么从哪里走到入口节点需要a步?答案是链表头节点head。

        下面求入环节点:令 fast 重新指向链表头部节点。此时 f=0,s=nb。slow 和 fast 同时每轮向前走 1 步。当 fast 指针走到 f=a 步时,slow 指针走到 s=a+nb 步。此时两指针重合,并同时指向链表环入口,返回 slow 指向的节点即可。

核心代码:

/**
 * 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) {
         unordered_set book;
         while (head != NULL) {
            if (book.count(head)) {
                return head;
            }
            book.insert(head);
            head = head->next;
        }
        return NULL;
    }
};

今日总结

        经过这几天的学习,我更加深刻的意识到做这些数据结构类的题目,只要对这个结构有足够深刻的理解,其实很容易就能解决问题。思路上不难想,有些时候写不出来是因为代码能力差,那就需要进一步学习C++的语法,同时需要多谢代码锻炼码力。

        今日学习时长6h,做题手感变好,思路更加清晰,感觉状态慢慢回暖。

        看了一部分C++八股文,没背,只进行了理解。

你可能感兴趣的:(算法,leetcode,c++)