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

文档讲解
双指针 哈希表 哨兵结点 循环不变式
核心:对于反转链表的问题,使用的pre和cur这两个指针,反转开始之前一定是pre指向上一段的最后一个节点,而cur指向即将反转链表的头节点,这就是所谓的循环不变式。那么又有一个问题,怎么保持住链表不断连呢?我们需要保存一个节点的信息,那就是要反转的链表前一个结点的信息,保存在P0中,这又有一个问题,例如昨天做的反转整个链表的问题,从第一个结点就开始反转,那P0去保存什么呢?故这个时候需要引入哨兵结点,P0保存它的信息。

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

思路:这道题可以把两个结点看成一个子链表,即我们翻转一个链表,其大小为2,那么从上面就很清晰的看出需要两个指针,一个指向链表的前一个位置,一个指向链表的头节点,(因为初始化的时候,pre没有地方指,故创建一个哨兵结点),那么在循环中,我们应该怎么样写呢?
1.将pre的next指针指向翻转之后的头节点;
2.cur的next指针指向剩下的链表;
3.翻转链表操作
4.移动两个指针,维持循环不变式,将pre移动到这个链表的末尾;
5.cur移动到下一个链表的开头
时间复杂度:O(n)
空间复杂度:O(1)

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy=new ListNode(0,head);
        ListNode* pre=dummy;
        ListNode* cur=dummy->next;
        while(pre&&cur&&cur->next)
        {
            pre->next=cur->next;
            cur->next=pre->next->next;
            pre->next->next=cur;
            pre=cur;
            cur=pre->next;
        }
        return dummy->next;
    }
};

后面拓展两道题:
92.反转链表ii
代码随想录算法训练营第四天|24.两两交换链表中的节点、19.删除链表的倒数第N个结点、面试题02.07.链表相交、142.环形链表ii_第1张图片
代码随想录算法训练营第四天|24.两两交换链表中的节点、19.删除链表的倒数第N个结点、面试题02.07.链表相交、142.环形链表ii_第2张图片
代码随想录算法训练营第四天|24.两两交换链表中的节点、19.删除链表的倒数第N个结点、面试题02.07.链表相交、142.环形链表ii_第3张图片

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode* dummy=new ListNode(0,head);
        ListNode* p0=dummy;
        for(int i=0;i<left-1;i++)
        {
            p0=p0->next;
        }
        //1<=left
        ListNode* pre=nullptr;
        ListNode* cur=p0->next;
        for(int i=0;i<right-left+1;i++)
        {
            ListNode* temp=cur->next;
            cur->next=pre;
            pre=cur;
            cur=temp;
        }
        p0->next->next=cur;
        p0->next=pre;
        return dummy->next;
    }
};

25.K个一组翻转链表

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        int l_size=0;
        ListNode* dummy=new ListNode(0,head);
        ListNode* p0=dummy;
        for(ListNode* cur=head;cur;cur=cur->next)
        {
            ++l_size;
        }
        ListNode* cur=p0->next;
        ListNode* pre=nullptr;
        for(;l_size>=k;l_size-=k)
        {
            for(int i=0;i<k;i++)
            {
                ListNode* temp=cur->next;
                cur->next=pre;
                pre=cur;
                cur=temp;
            }
            ListNode* temp=p0->next;
            p0->next->next=cur;
            p0->next=pre;
            p0=temp;
        }
        return dummy->next;
    }
};

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

思路:说实话,我拿到这道题的时候,第一瞬间是想到了双指针的,但是没想出来,所以想把遍历一遍,大小求出来,把边界考虑清楚了,做起来还是很容易的,但是代码随想录这种做法还是得学!(感觉学了也很难用出来)
时间复杂度:O(n)
空间复杂度:O(1)

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) 
    {
        ListNode* dummy=new ListNode(0,head);
        int l_size=0;
        for(ListNode* cur=head;cur;cur=cur->next)
        {
            ++l_size;
        }
        int m=l_size-n;
        if(m<0)return dummy->next;
        ListNode*cur=dummy;
        while(m--)
        {
            cur=cur->next;
        }
        ListNode* temp=cur->next;
        cur->next=cur->next->next;
        delete temp;
        return dummy->next;
    }
};

双指针算法做这道题,此题的双指针之间的距离就是来维持题目需要的n,如果快指针到达末尾了,那慢指针距离快指针的距离正好为n的话,这个时候是不是慢指针就是指向要删除的结点的呢?但我们要删除一个结点,应该知道他的上一个结点,故要保持距离为n+1!
时间复杂度:O(n)
空间复杂度:O(1)

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy=new ListNode(0,head);
        ListNode* fast=dummy;
        ListNode* slow=dummy;
        while(n--&&fast!=nullptr)
        {
            fast=fast->next;
        }
        //对于这里的处理有两种写法
        /*
         while(fast->next)
        {
            fast=fast->next;
            slow=slow->next;
        }
        */
        fast=fast->next;//维持slow与fast之间的距离是n+1,让slow最后停在倒数第n+1的这个位置
        while(fast)
        {
            fast=fast->next;
            slow=slow->next;
        }
        slow->next=slow->next->next;
        return dummy->next;
    }
};

上面的代码随想录的答案,fast多走一步,来维持长度为n+1。其实我觉得这里不用这样处理!其中注释一个代码,换成另外一种也是可以的在这里fast指针不再到链表的外,而是到链表的最后一个元素,此时长度也是n,但是slow依然会停在倒数第n+1的位置!
面试题02.07.链表相交

思路:其实一刷这道题的时候,是懵逼的,不懂他想表达啥意思。其实这道题他就是想你去找到较大链表某个位置开始,找到末尾和较小链表从某个位置开始找到末尾,有没有一样的一段,只要第一个节点一样后面的都一样了,但是这个题还有一个坑比的地方,就是链表是否相交是比较的地址,这个是啥意思呢?用第一个leetcode上面第一个例子来举例,两个链表都是从1这个结点的next指针指向的下一个结点,那么这个时候下一个结点应该就是同一个结点了,而不是比较数值。简直是大坑啊!!,1和1前一个结点不一样,说明1和1只是存放的数值相同的不同结点,故不能判断那里的!
时间复杂度:O(n)
空间复杂度:O(1)

class Solution {
public:
    int getlength(ListNode* head)
    {
        int length=0;
        for(ListNode* cur=head;cur;cur=cur->next)
        {
            ++length;
        }
        return length;
    }
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lenA=getlength(headA);
        int lenB=getlength(headB);
        if(lenB>lenA)
        {
            swap(headA,headB);
            swap(lenA,lenB);
        }
        int n=lenA-lenB;
        while(n--)
        {
            headA=headA->next;
        }
        ListNode* cur1=headA;
        ListNode* cur2=headB;
        int flag=0;
        while(cur1!=nullptr)
        {
            if(cur1==cur2)
            {
                return cur1;
            }
            cur1=cur1->next;
            cur2=cur2->next;
        }
        return nullptr;
    }
};

142.环形链表ii

思路:1.第一种哈希表的做法,这个题一拿到就想到了hashmap;2.双指针破局!
时间复杂度:O(n)
空间复杂度:O(n)

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_map<ListNode*,int> map;
        while(head!=nullptr)
        {
            if(map[head]!=0)
            {
                return head;
            }
            map[head]++;
            head=head->next;
        }
        return nullptr;
    }
};

双指针算法:使用快慢指针,如何体现快慢指针呢快指针每次走两步,慢指针每次只走一步!876.链表的中间结点这道题可以感觉一下这种用法。141.环形链表这道题较这个也会容易一点,这个题最关键的点在于如何求出入口呢?这个时候需要数学推导一下!

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

数学推导:快指针走过的路程:a+k(b+c);慢指针走过的路程:a+b
2(a+b)=a+b+c+(k-1)(b+c);=>a-c=(k-1)(b+c),这个等式啥意思呢?a是从头节点到入口的距离,从头节点走c步,剩下的距离是环状的整数倍,相当于这个整数倍对于实际位置没有影响,那么我们有一个指针从某一位置到入口的距离也是c,那就是慢指针,可能有人这个时候会觉得快指针不也是在那个位置吗?但是你别忘了,快指针一次是走两步的,所以头节点走到a-c这个位置的时候,慢指针一定是走到环的入口的。故接下来只需要头节点往前走,slow走就行了,他们相遇的时候一定是环状链表的入口!
时间复杂度:O(n)
空间复杂度:O(1)加粗样式

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow=head;
        ListNode* fast=head;
        while(fast!=nullptr&&fast->next!=nullptr)
        {
            slow=slow->next;
            fast=fast->next->next;
            if(slow==fast)
            {
                while(head!=slow)
                {
                    head=head->next;
                    slow=slow->next;
                }
                return slow;
            }
        }
        return nullptr;
    }
};

你可能感兴趣的:(算法,链表,数据结构)