大一计算机的自学总结:链表相关题目

前言

链表题写起来有种纯看数值的美,没什么高深的算法思路,全看过硬的coding能力。(捂脸)

一、相交链表

/**
 * 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) {
        if(headA==NULL||headB==NULL)
        {
            return NULL;
        }
        ListNode* a=headA,* b=headB;
        int diff=0;//记录两链表长度差值
        while(a!=NULL)
        {
            a=a->next;
            diff++;
        }
        while(b!=NULL)
        {
            b=b->next;
            diff--;
        }
        if(a!=b)
        {
            return NULL;
        }
        if(diff>=0)
        {
            a=headA;
            b=headB;
        }
        else
        {
            a=headB;
            b=headA;
        }
        diff=diff<0?-diff:diff;
        while(diff!=0)
        {
            a=a->next;
            diff--;
        }
        while(a!=b)
        {
            a=a->next;
            b=b->next;
        }
        return a;
    }
};

 看到这个题的第一思路就是两个指针分别往中间跳,跳到相同的节点就是相交节点。

考虑到两链表长度不一,所以设置diff变量记录两链表长度差值。思路是遍历第一个链表让diff++,然后遍历第二个链表让diff--,此时若a和b不相等,说明根本没有相交节点。之后取a为长链表,然后diff取绝对值。然后先让长链表走差值个距离,再两个指针一起跳即可。

二、K 个一组翻转链表

/**
 * 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* reverseKGroup(ListNode* head, int k) {
        ListNode* start=head;
        ListNode* end=head;

        end=teamEnd(start,k);
        if(end==NULL)
        {
            return head;
        }

        head=end;//第一组要先处理
        reverse(start,end);
        ListNode* lastTeamEnd=start;
        while(lastTeamEnd->next!=NULL)
        {
            start=lastTeamEnd->next;
            end=teamEnd(start,k);
            if(end==NULL)
            {
                return head;
            }
            reverse(start,end);
            lastTeamEnd->next=end;
            lastTeamEnd=start;
        }
        return head;
    }

    ListNode* teamEnd(ListNode* start,int k)
    {
        // for(int i=1;inext;
        // }
        while(--k>0&&start!=NULL)
        {
            start=start->next;
        }
        return start;
    }

    void reverse(ListNode* start,ListNode* end)
    {
        end=end->next;
        ListNode* last=NULL;
        ListNode* cur=start;
        ListNode* nextNode=NULL;
        while(cur!=end)
        {            
            nextNode=cur->next;
            cur->next=last;
            last=cur;
            cur=nextNode;
        }
        start->next=end;
    }
};

 这个题就有点难度了,主要不是思路上的,而是纯写代码上的,总是莫名其妙就报错了。TvT

就像上文提的,思路不难,就是K个一组反转,然后原本的头节点去连下一组的尾节点。反转链表的操作可以看我的这篇文章。大一计算机的自学总结:单双链表的反转

略有不同的是,这里要先记end的下一个节点,最后记得让start节点和end的下一个节点相连。

为了划分K个一组,设置teamEnd函数,返回从头节点开始的K个节点。

注意,这里为了要传回更新后的head节点,所以要对第一组数据进行特殊处理,还要记得特殊情况,若链表长度不足一组就直接返回head节点。(心累)

接着设置lastTeamEnd变量记上一组更新后的尾节点,为了找下一组的开头以及最后和下一组的更新前的尾节点相连。

三、随机链表的复制

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head==NULL)
        {
            return NULL;
        }
        Node* cur=head;
        Node* next=NULL;
        while(cur!=NULL)
        {
            next=cur->next;
            cur->next=new Node(cur->val);
            cur->next->next=next;
            cur=next;
        }
        cur=head;
        Node* copy=NULL;
        while(cur!=NULL)
        {
            next=cur->next->next;
            copy=cur->next;
            copy->random=cur->random!=NULL?cur->random->next:NULL;
            cur=next;
        }
        Node* ans=head->next;
        cur=head;
        while(cur!=NULL)
        {
            next=cur->next->next;
            copy=cur->next;
            cur->next=next;
            copy->next=next!=NULL?next->next:NULL;
            cur=next;
        }
        return ans;
    }
};

 这个题主要是题目比较难懂,思路就是让复制节点跟在原节点后,这样就能通过原节点的random指针找到复制节点的random指针的位置。

第一个循环,在每个节点的后面建立一个复制的节点,并让它们相连。

之后让cur回到head,进行第二个循环,让复制节点的random指针等于cur节点的random指针。

最后让ans等于复制的头节点,进行第三个循环,让复制的节点和原节点断连,形成复制的链表。

四、回文链表

/**
 * 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:
    bool isPalindrome(ListNode* head) {
        if(head==NULL||head->next==NULL)
        {
            return true;
        }
        ListNode* slow=head,* fast=head;
        while(fast->next!=NULL&&fast->next->next!=NULL)
        {
            slow=slow->next;
            fast=fast->next->next;
        }
        ListNode* last=slow;
        ListNode* cur=last->next;
        ListNode* next=NULL;
        last->next=NULL;
        while(cur!=NULL)//逆序
        {
            next=cur->next;
            cur->next=last;
            last=cur;
            cur=next;
        }
        ListNode* left=head;
        ListNode* right=last;
        while(left!=NULL&&right!=NULL)
        {
            if(left->val!=right->val)
            {
                return false;
            }
            left=left->next;
            right=right->next;
        }
        // 还原
        // cur=last->next;
        // last->next=NULL;
        // next=NULL;
        // while(cur!=NULL)
        // {
        //     next=cur->next;
        //     cur->next=last;
        //     last=cur;
        //     cur=next;
        // }
        return true;
    }
};

 思路很好想,就是让链表的后半部分反转,然后一个一个节点对比就行。

这里,重点是找一个链表中点的方法:快慢指针。快慢指针就是在头节点放置一个快指针一个慢指针,快指针每次跳两步,慢指针每次跳一步,这样当快指针走到尾节点时慢指针的位置就是中点

之后反转后半部分链表,然后一个一个节点比较即可。

(最后也可以加上还原后半部分的代码。)

五、环形链表 II

/**
 * 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) {
        if(head==NULL||head->next==NULL||head->next->next==NULL)
        {
            return NULL;
        }
        ListNode* slow=head->next;
        ListNode* fast=head->next->next;
        while(slow!=fast)
        {
            if(fast->next==NULL||fast->next->next==NULL)
            {
                return NULL;
            }
            slow=slow->next;
            fast=fast->next->next;
        }
        fast=head;
        while(slow!=fast)
        {
            slow=slow->next;
            fast=fast->next;
        }
        return slow;
    }
};

 这个题也用到了快慢指针,原理是,快慢指针还是都从头节点出发,快指针一次走两步,慢指针一次走一步,若有环,则快慢指针必相遇;相遇后让快指针回到头节点,改为一次走一步,当快慢指针再次相遇时就是入环节点

除此之外还需要注意特殊情况的判断,若快指针能走到NULL说明链表没有环。

六、排序链表

/**
 * 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* start;
    ListNode* end;

    void merge(ListNode* l1,ListNode* r1,ListNode* l2,ListNode* r2)
    {
        ListNode* last;
        if(l1->val<=l2->val)
        {
            start=l1;
            last=l1;
            l1=l1->next;
        }
        else
        {
            start=l2;
            last=l2;
            l2=l2->next;
        }

        while(l1!=NULL&&l2!=NULL)
        {
            if(l1->val<=l2->val)
            {
                last->next=l1;
                last=l1;
                l1=l1->next;
            }
            else
            {
                last->next=l2;
                last=l2;
                l2=l2->next;
            }
        }
        if(l1!=NULL)
        {
            last->next=l1;
            end=r1;
        }
        else
        {
            last->next=l2;
            end=r2;
        }
    }

    ListNode* findEnd(ListNode* s,int step)
    {
        for(int i=1;inext!=NULL;i++)
        {
            s=s->next;
        }
        return s;
    }

    ListNode* sortList(ListNode* head) {
        if(head==NULL||head->next==NULL)
        {
            return head;
        }
        end=head;
        int n=0;
        while(end!=NULL)
        {           
            n++;
            end=end->next;
        }
        ListNode* l1,* r1,*l2,*r2,* next,* lastTeamEnd;
        for(int step=1;stepnext;
            if(l2==NULL)
            {
                break;
            }
            r2=findEnd(l2,step);
            next=r2->next;
            r1->next=r2->next=NULL;
            merge(l1,r1,l2,r2);
            head=start;
            lastTeamEnd=end;
            while(next!=NULL)
            {
                l1=next;
                r1=findEnd(l1,step);
                l2=r1->next;
                if(l2==NULL)
                {
                    lastTeamEnd->next=l1;
                    break;
                }
                r2=findEnd(l2,step);
                next=r2->next;
                r1->next=r2->next=NULL;
                merge(l1,r1,l2,r2);
                lastTeamEnd->next=start;
                lastTeamEnd=end;
            }
        } 
        return head;      
    }
};

在我的文章大一计算机的自学总结:基数排序中有写排序算法的稳定性。在数组中,不存在时间复杂度为O(n*logn),空间复杂度为O(1)而且还有稳定性的算法,但链表中存在,就是链表的归并排序,归并排序的内容可见我的文章大一计算机的自学总结:归并排序及归并分治。

因为要求空间复杂度为O(1),所以连递归都不能调用。(憔悴)

首先要找到链表的尾节点,还要借助findEnd函数找step长度的尾节点。因为要返回头节点,所以要对第一组step先进行特殊处理。这里还要设置四个变量分别表示两段step的头尾。

一定!一定!一定不要忘了讨论边界情况!!!(大声)

之后的操作大同小异,重点还是merge函数。使用尾插法,在确定头节点后进行尾插,最后记得设置尾节点end。

这个题真是思路好想代码难写,一道题写完感觉心力憔悴……

总结

链表的题目就是菜就多练,没啥高难的思路算法,就是纯看代码能力。(瘫)

END

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