链表牛客网中等、较难题(C++)

链表部分总体小节

1:链表部分,要学会多创建一些头指针,多个之间的互补使用衔接
2:学会巧使用pre->next=head,便于做到对后续的统一判断
3:快指针、慢指针的问题,快指针同时走两步是慢指针的2倍,可以用来判断是否有环,有环总是能撵上的
4:倒数第几个的问题,快指针先走第n步,则慢指针再走,两个链表可以很快地定位到倒数第n位
5:链表之间的赋值关系要慎重,因为很容易造成链表混乱,学习后面的方法,一种是直接对链表反转,一种是单个对内部的进行翻转,第二种自己理解花了很久,采用新建temp重新创造关系,以及指针next移动根部不动的方法自己要多深入体会
6:题目1中的算法要记住
7:要记得对链表的末尾加NULL不要忘记
8:多个头节点有的时候就是为了维护同一个链表,所有通常情况下都是不对head直接进行操作的,需要进行保留,只要少部分递归的情况需要直接用head,再者就是pre节点,最后返回pre->next的问题,虽然最开始定义的是pre->next=head, cur=head但是并不代表最终运算完返回pre->next还是等于head了,因为head会一直不变,但是pre->next也许会根据实际情况为空也是可能的,还是要看代码编写情况再确定!!!
总而言之~~~~~链表确实是比树会难一些,树基本不会修改删除一些树之间的关系,几乎就是求深度,最短路径等等,而链表则就要多一些插入删除的操作,其因为其简洁的形式,也多了一些更多结合使用的功能

题目1(链表的环入口点)

在这里插入图片描述

收获

在这里插入图片描述
在这里插入图片描述
1:这道题最重要的思想是第一次先找到相遇点,接下来快的从头开始遍历到入口的距离=相遇点到入口的距离+(k-1)圈圆环长度

代码

class Solution {
public:
//     算法的思路很重要!!!
    ListNode *iscycle(ListNode *head){
        if(head==NULL)
            return NULL;
        ListNode *fast=head;
        ListNode *slow=head;
        while(fast!=NULL&&fast->next!=NULL){
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow)
                return slow;
        }
        return NULL;
    }
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        ListNode *slow=iscycle(pHead);
        if(slow==NULL)
            return NULL;
        ListNode *fast=pHead;
        while(fast!=slow){
            fast=fast->next;
            slow=slow->next;
        }
        return slow;
    }
};

题目2(删除链表的倒数第n个节点)在这里插入图片描述

收获

1:这道题总体的思想还是快链表先走,慢链表记录前一个位置方便衔接
2:自己独立完成,较好

代码

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param n int整型 
     * @return ListNode类
     */
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        // write code here
        ListNode *fast=head;
        //快链表先走n个,之后慢链表从头遍历并记住前一个选项用于删除
        for(int i=0;i<n;i++){
            fast=fast->next;
        }
        //如果没有这句话的话后期的pre没有初值不能使用next函数!!
        if(fast==nullptr)
            return head->next;
           //这里的head->next通常就是删除倒数第一个,所以直接返回第二个
        ListNode *slow=head;
        ListNode *pre;
        while(fast){
            pre=slow;
            fast=fast->next;
            slow=slow->next;
        }
        //循环结束pre指向的就是第n-1个节点!!
        pre->next=slow->next;
        return head;
    }
};

题目3(链表相加2)

在这里插入图片描述

收获

1:这道题在中间记录的过程中,本打算直接对得到的结果进行相反,忘记记录最初的位置了,导致不能很好的翻转是一个小失误,后来自己也及时改进
2:觉得自己对链表的排序合并,以及相加减移动这方面已经较熟悉了

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head1 ListNode类 
     * @param head2 ListNode类 
     * @return ListNode类
     */
    ListNode *reverseLink(ListNode *head){
        if(head==NULL)
            return NULL;
        ListNode *pre=NULL;
        ListNode *cur=head;
        while(cur){
            ListNode *temp=cur->next;
            cur->next=pre;
            pre=cur;
            cur=temp;
        }
        return pre;
    }
    ListNode* addInList(ListNode* head1, ListNode* head2) {
        // write code here
        if(head1==nullptr)
            return reverseLink(head2);
        if(head2==nullptr)
            return reverseLink(head1);
        //将前两个链表反转
        ListNode *first=reverseLink(head1);
        ListNode *second=reverseLink(head2);
        int jinwei=0;
        ListNode *third=nullptr;
        //如果不用这个result记录的话最后的third的链表方向反了,并且在创建头链表的时候要记得创建
        //第一个的特殊用法~
        ListNode *result;
        while(first&&second){
            if(third==nullptr){
                third=new ListNode((first->val+second->val+jinwei)%10);
                result=third;
                jinwei=(first->val+second->val+jinwei)/10;
            }else{
                third->next=new ListNode((first->val+second->val+jinwei)%10);
                third=third->next;
                jinwei=(first->val+second->val+jinwei)/10;
            }
            first=first->next;
            second=second->next;
        }
        while(first){
            third->next=new ListNode((first->val+jinwei)%10);
            jinwei=(first->val+jinwei)/10;
            third=third->next;
            first=first->next;
        }
        while(second){
            third->next=new ListNode((second->val+jinwei)%10);
            jinwei=(second->val+jinwei)/10;
            third=third->next;
            second=second->next;
        }
        if(jinwei){
            third->next=new ListNode(1);
            third=third->next;
        }
        return reverseLink(result);
    }
};

题目4(单链表的排序)

在这里插入图片描述

收获

1:相较于直接用链表排序的复杂,中间用vector数组辅助排序出的结果,速度比较快,完全自己独立完成

代码

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 the head node
     * @return ListNode类
     */
    //!!!!!!!aaaaa这是错觉么???好有成就感的感觉!!!!!!!!!!!!本来打算用数组的,但是确实没有
    //vector直接排序比较nice!!!
    ListNode* sortInList(ListNode* head) {
        // write code here
        if(head==nullptr||head->next==nullptr)
            return head;
        ListNode *temp=head;
        //创建一个
        vector<int >res;
        while(temp){
            res.push_back(temp->val);
            temp=temp->next;
        }
        sort(res.begin(), res.end());
        ListNode *returnnum=new ListNode(res[0]);
        temp=returnnum;
        for(int i=1;i<res.size();i++){
            temp->next=new ListNode(res[i]);
            temp=temp->next;
            temp->next=NULL;
        }
        return returnnum;
    }
};

题目5(链表的奇偶重排)

在这里插入图片描述

收获

1:这道题注意题目要求即可,是节点的编号奇数一边,偶数一边,不要直接对节点数据进行判断了

代码

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 *	ListNode(int x) : val(x), next(nullptr) {}
 * };
 */
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* oddEvenList(ListNode* head) {
        // write code here
        if(head==nullptr||head->next==nullptr)
            return head;
        ListNode *temp=head;
        vector<int >jishu;
        vector<int >oushu;
        int i=0;
        while(temp){
            i++;
            if(i%2==0)
                oushu.push_back(temp->val);
            else
                jishu.push_back(temp->val);
            temp=temp->next;
        }
        ListNode *node=new ListNode(jishu[0]);
        temp=node;
        for(int j=1;j<jishu.size();j++){
            temp->next=new ListNode(jishu[j]);
            temp=temp->next;
            temp->next=nullptr;
        }
        for(int j=0;j<oushu.size();j++){
            temp->next=new ListNode(oushu[j]);
            temp=temp->next;
            temp->next=nullptr;
        }
        return node;
    }
};

题目6(删除有序链表中重复的元素)

在这里插入图片描述

收获

1:这道题相比于前面的删除掉只剩一个重复的而言,是需要把有重复的就删掉,于是就要记录第一个有重复的位置,一直将指针后移,直到没有重复为止
2:巧用链表之前的pre!

代码

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* deleteDuplicates(ListNode* head) {
        // write code here
        if(head==nullptr||head->next==nullptr)
            return head;
        ListNode *pre=new ListNode(0);
        pre->next=head;
        ListNode *cur=pre;
        while(cur->next!=NULL&&cur->next->next!=NULL){
            if(cur->next->val==cur->next->next->val){
                int temp=cur->next->val;
                while(cur->next!=NULL&&cur->next->val==temp)
                    cur->next=cur->next->next;
            }
            else{
                cur=cur->next;
            }
        }
        return pre->next;
    }
};

题目7(链表内指定区间反转)

在这里插入图片描述

收获

1:这道题真的是本来最开始遇到的就是这题,但是最开始对链表的理解不是很深入,就不是很能理解,后来慢慢的理解了,就是最后的链接怎么将翻转的最后一位,和没翻转的下一位接上的问题,后来意识到原来是cur指针一直不动,利用cur->next,再新建temp进行链接实现的功能(印象很深刻了~)

代码

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param m int整型 
     * @param n int整型 
     * @return ListNode类
     */
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        // write code here
        ListNode* res=new ListNode(-1);
        res->next=head;
        ListNode *pre=res;
        //当前节点!!
        ListNode  *cur=head;
        for(int i=1;i<m;i++){
            pre=cur;
            cur=cur->next;
        }
        //一直在移动指针,其实真正的操作是用temp和temp->next进行衔接!!!而且cur的位置一直
        //保持不变,正好对应的遍历在最后的时候的cur->next进行衔接!!!!呜呜呜
        for(int i=m;i<n;i++){
            //创建一个新temp等于当前的下一个,用于连接
            ListNode * temp=cur->next;
            //记录好下一次的位置,方便下一次循环用
            cur->next=temp->next;
            //建立好连接temp后是pre的下一个
            temp->next=pre->next;
            //pre的下一个是temp,通过这样就使得pre的next一直向前到翻转链表的尾部·temp之间在一点
            //一点的连接
            pre->next=temp;
        }
        return res->next;
    }
};

题目8(链表中的节点每 k 个一组翻转)

在这里插入图片描述

收获

1:这道题不是自己写的,题解用递归的思想实现,很方便,自己也理解了~

代码

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    //唔~理解起来还比较方便!!递归的想法!
    ListNode* reverseKGroup(ListNode* head, int k) {
        // write code here
        ListNode *tail=head;
        //遍历k次到尾部,实际上是下一组k的开始!!,注意判断的顺序
        for(int i=0;i<k;i++){
            if(tail==NULL)
                return head;
            tail=tail->next;
        }
        ListNode *pre=NULL;
        ListNode *cur=head;
        while(cur!=tail){
            ListNode *temp=cur->next;
            cur->next=pre;
            pre=cur;
            cur=temp;
        }
        head->next=reverseKGroup(tail, k);
        return pre;
    }
};

题目9(合并 k 个已排序的链表)(hard)

在这里插入图片描述

收获

1:这道题最开始的思路,时间复杂度大概是k²,而不是题目所要求的log形式,自己想到了可以使用归并排序,但是不是很熟悉,看了题解,觉得自己应该可以写出来,链表和递归的结合的一种~~~

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
//     ListNode *sortLink(ListNode *head,ListNode* head2){
        
//     }
//     ListNode *mergeKLists(vector<ListNode *> &lists) {
//         if(lists.size()==1)
//             return lists[0];
//         //将对多个进行排序转换为排序n-1次,每次对两个进行升序排序,但是这么做的结果就是
//         //时间复杂度不满足要求,最好能同时排序两个,有一种归并排序的思想
//         ListNode *head=lists[0];
//         for(int i=1;i<lists.size();i++){
//             head=sortLink(head,lists[i]);
//         }
//         return head;
//     }
    ListNode *sortLink(ListNode *head1,ListNode* head2){
        if(head1==NULL)
            return head2;
        if(head2==NULL)
            return head1;
        ListNode *head=new ListNode(0);
        ListNode *cur=head;
        while(head1&&head2){
            if(head1->val<head2->val)
            {
                cur->next=head1;
                head1=head1->next;
            }
            else
            {
                cur->next=head2;
                head2=head2->next;
            }
            cur=cur->next;    
        }
        if(head1){
            cur->next=head1;
        }
        if(head2){
            cur->next=head2;
        }
        return head->next;
    }
    //归并排序的思想,一会儿可以练习练习,但是现在要去总结啦~~~
    ListNode *mergeLink(vector<ListNode *>&lists,int left,int right){
        if(left>right)
            return NULL;
        else if(left==right)
            return lists[left];
        int mid=(left+right)/2;
        return sortLink(mergeLink(lists, left, mid),mergeLink(lists, mid+1, right));
    }
    
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        return mergeLink(lists, 0, lists.size()-1);
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值