leetcode(一) 链表专题

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

思路:因为要求只用一趟扫描,所以采用双指针算法
1.由于题目没有给定头结点,建立一个虚拟节点head避免删除头结点时的问题
2.使first和second指针距离n+1,然后同时向后移动两个指针,直到second指向null
3.删除first后面的节点

在这里插入图片描述
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {

       auto dummy=new ListNode(-1);        //创建虚拟头结点

       dummy->next=head;

       auto first=dummy,second=dummy;      //注意双指针的初值必须赋值成虚拟头结点,否则无法正确删除第一个节点

       while(n--)second=second->next;     //second 后移n位

       while(second->next){

           first=first->next;

           second=second->next;
       }

       first->next=first->next->next;       //删除倒数第n个节点

       return dummy->next;                  //返回头结点

        
    }
};

237.删除链表中的节点

删除链表中任意给定的节点,由于不知道前一个指针的指向,只需要把当前节点复制成后一个节点,然后再把后一个节点删除即可
1.复制node->next的值给node
2.删除node->next
在这里插入图片描述

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void deleteNode(ListNode* node) {

        node->val=node->next->val;

        node->next=node->next->next;  //此处可以利用c++特性讲两语句合并成一句*(node)=*(node->next);  作用与两句话相同
        
    }
};

83.删除排序链表中的重复元素

思路:让指针P从头往后遍历,遇到val相同时则删除后一个节点,遇到不同时则指向下一个节点
在这里插入图片描述

在这里插入图片描述


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

        if(!head)return head;             //节点为空返回

        ListNode * P=head;

        while(P->next){                  //这里写P->next防止报空指针错误(如果P指向最后一个节点,那么P->next—>val会出现null.val的空指针错误)

            if(P->val==P->next->val)P->next=P->next->next;

            else  P=P->next;

        }

        return head;
      }
};

61.旋转链表

思路:利用双指针算法
1.k%n去掉不必要的移动步骤
2.first走k步
3.first和second同时向后走,直到first指向末尾节点,此时second即为倒数第k个节点
4.first指向head,second指向null即可

在这里插入图片描述
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if(!head)return NULL;
        
        //遍历得到节点总数
        int n=0;

        for(auto P=head;P;P=P->next)n++;

        k%=n;

        //first是后节点,second是前节点
        auto first =head,second=head;

        while(k--)first=first->next;

        while(first->next){

            first=first->next;

            second=second->next;
        }
        
        //后节点指向头,前节点断开指null
        first->next=head;

        head=second->next;

        second->next=NULL;

        return head;

        
    }
};

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

思路:先建立虚拟节点dummy
1.a,b指向要交换的两个节点
2.执行以下步骤
3.由于p的位置不会变化,移动p到a的位置,重复上述步骤

在这里插入图片描述
在这里插入图片描述

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

        if(!head)return head;

        auto dummy=new ListNode(0);

        dummy->next=head;

        for(auto p=dummy;p->next&&p->next->next;){  //保证p的下一个及下下个节点都存在

            auto a=p->next,b=a->next;

            p->next=b;

            a->next=b->next;

            b->next=a;                //交换

            p=a;                      //p移动到下一个要交换的一对节点前
        }
        
        return dummy->next;
    }
};


206.反转链表

思路:题意需要反转链表,采用将从头到尾的节点指向全部倒置,最后取尾节点做新头结点的方法
1.初始化:a指向头,b指向a->next,a->next指向null
2.while(b)c指向b->next
3.交换指向b->next=a,然后a,b,c往后移动一位
4.返回a

在这里插入图片描述
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        
        if(!head)return head;

        auto a=head,b=a->next;

        a->next=NULL;              //原头结点指向null

        while(b){

            auto c=b->next;        //反转链表

            b->next=a;

            a=b,b=c;
        }

        return a;
    }
};


92.反转链表II

思路如图所示,由于m,n使用过,用b,d代替
并且在反转链表时用p,q,o表示相邻的三个节点
在这里插入图片描述
在这里插入图片描述


class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {

        if(m==n)return head;

        auto dummy =new ListNode(0);

        dummy->next=head;

        auto a=dummy,d=dummy;

        for(int i=0;i<m-1;i++)a=a->next;

        for(int i=0;i<n;i++)d=d->next;

        auto b=a->next,c=d->next;

        auto p=b,q=p->next;
        
        //反转链表,同上题,pqo是交换时的三个节点
        while(q!=c){
           auto o=q->next;

           q->next=p;

           p=q,q=o;     
        }

        b->next=c;

        a->next=d;
        
        return dummy->next;
        
    }
};

160.相交链表 **

思路:用两个指针分别从头开始扫描,每次只走一步,当走到null时仍然没有相遇,则交换头结点扫描,直到相遇 为止
1.扫描两个不相交的链表,则交换头结点后最后两个指针同时指向null,两节点均扫描a+b步
2.若扫描两个相交链表若链表1长a+c,链表2长b+c
则最坏情况下扫描a+b+c时两指针相遇,当a=b时,两节点会提前相遇

在这里插入图片描述
在这里插入图片描述


/**
 * 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) {

        auto p=headA,q=headB;

        while(p!=q){
            if(p)p=p->next;
            
            else p=headA;

            if(q)q=q->next;

            else q=headB;
        }

        return p;
        
    }
};

142.环形链表II **

思路:如图设置一个快指针fast一个慢指针slow,快指针每次走两步,慢指针每次走一步
1.如果快指针会无路可走(指向null)说明不存在环
2.否则让快慢指针同时前进,fast先进圈,当slow到达b时,fast在里面走了x位于c,假设c距离b是y,此时让fast开始追slow,那么fast会在距离b点y的对称点c又追上slow,这时让fast回到起点a,两指针开始以相同速度向后移动,则最后会相遇在b点
注解:相当于将相遇点c向后转动x正好到达环入口

在这里插入图片描述
在这里插入图片描述

/**
 * 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) {
        auto fast=head,slow=head;

        while(fast){
            slow=slow->next;

            fast=fast->next;

            if(fast)fast=fast->next;

            else  break;              //不存在环,跳出

            if(fast==slow){          //首次相遇
                fast=head;

                while(fast!=slow){    //再次相遇
                    fast=fast->next;

                    slow=slow->next;
                }

                return fast;
            }

        }

        return NULL;
        
    }
};


148.排序链表**

因为题目要求的时间复杂度是O(nlogn)空间复杂度是O(1)
所以满足的排序方式只有快排和归并,但又不能调用系统栈…所以只能手写归并

思路:自底向上的归并排序,就是原来用递归去写,是系统调用栈完成的循环,现在直接改自己用循环手写了

在这里插入图片描述

  • 另外说明一下特殊情况,以链表长度是7这种特殊情况为例:
  • 当要把区间分为长度是4时,第二个区间长度只有3,但是依然对这个区间进行归并排序,(因为可以分成左2右1的两个部分),但如果 链表长度是6,第二个区间长度只有2,不满足条件,所以第二个区间就不进行排序了,(实际上已经由上一层归并排序完成)

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        auto dummy =new ListNode(0);

        dummy->next=head;

        int n=0;

        for(auto p=head;p;p=p->next)n++;      //链表长   

        //从左右部分长度是1开始归并,区间长是2,往后加倍
        for(int i=1;i<n;i*=2){
            auto cur=dummy;                 //每次归并起点的前一个点

            //计算次数,例如i=2时,每次归并应当至少需要三个节点,至多四个节点,所以3<=n<=6时只排一次7<=n<=10排两次...
            for(int j=1;j+i<=n;j+=i*2){
                auto left=cur->next,right=cur->next;      //左右部分头结点

                for(int k=0;k<i;k++)right=right->next;    //调整右边部分指向
                
                int l=0,r=0;                             //l,r至多为左右部分长度,但右半部分长度可能不够

                while(l<i&&r<i&&right){

                    if(left->val<=right->val){
                        cur->next=left;

                        cur=left;

                        left=left->next;

                        l++;
                    }

                    else{
                        cur->next=right;

                        cur=right;

                        right=right->next;

                        r++;
                    }

                }
                
                //补足未插入的节点
                while(l<i){
                    cur->next=left;

                    cur=left;

                    left=left->next;

                    l++;
                }

                while(r<i&&right){
                    cur->next=right;

                    cur=right;

                    right=right->next;

                    r++;
                }
                //每次归并结束后right指向下一个区间的第一个节点
                cur->next=right;
            }
        }
        return dummy->next;
    }
};

另外附上链表归并排序的递归写法


/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if(!head||!head->next)return head;      //保证被排序区间至少有两个节点

        auto slow=head,fast=head->next;

        //快慢指针寻找中间节点
        while(fast->next&&fast->next->next){
            slow=slow->next;

            fast=fast->next->next;
        }
        
        //head2是归并右半部分
        auto head2=slow->next;

        slow->next=NULL;

        //递归处理左右两边
        head=sortList(head);

        head2=sortList(head2);

        //处理最后一层区间
        return merge(head,head2);
        
    }
    
    //归并排序
    ListNode * merge(ListNode* list1,ListNode* list2){

        auto dummy=new ListNode(0);
        
        auto cur=dummy;

        auto left=list1,right=list2;

        while(left&&right){
            if(left->val<=right->val){

                cur->next=left;

                left=left->next;

            }
            else{
                cur->next=right;

                right=right->next;
            }
            cur=cur->next;
        }
        if(left)cur->next=left;

        else cur->next=right;

        return dummy->next;

    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值