链表2 链表oj题解与专题总结

写完本篇  有一个想法:

考点与题目   是一对多关系,所以我觉得 一题多解,是考点与题目  是多对一关系。很多人说算法是一种思想,只要能解决题目就行。就像你排个序,能找出不只十种的排序方法都能做到。但是在投入生产中,我们在多不胜选的选择中却难以找到一直能“优胜”的算法。最终的选择总是权衡利弊,寻找最优解(或者处于这种做题状态中)。

结构决定性质。事物往往看似大同小异,但正是因为这一点点的“异”,才能把事物用到刀尖上。算法还是个很抽象的事物。看待事物的角度有多少种,我们就可以尝试多少种方法解决。即使算法与数据结构的学习,只是作为现阶段关注的任务,为了长远的收获,我还是认为极力的靠拢知识点的学习更有意义。


2.反转链表:

力扣https://leetcode-cn.com/problems/remove-linked-list-elements/

头插法

画图的作用,使写代码更直观。画出初始,过程,结束状态,写一个变量列表。这样我们就能清晰的知道我们需要操控那些变量,然后进而画出下一步用代码进行描述。 

 链表的考察,基础中的基础就是“增删改查”,从时间和空间上分成,空间


3.查找链表的中间节点 

快慢指针法

 直接用从快慢指针法去套模板不一定很快能想到快慢指针法。通过列了一个表达式本质就是描述了一个表达式中两个变量关系的一种解法:特点是两个变量有等式,知一求一。注意:这里的关系,可以是数学关系,抽象的整体与部分之间的关系,一个对象的始末态关系....等等。

越去抽象就越能灵活应用。有人常常说,快慢指针是容易记住的。的确,因为链表玩的就是指针。所以这种解法在链表的问题解决中是核心的。在后面的核心解法将会进行扩充。

 举一反三:查找链表中倒数第k个节点。


21. 合并两个有序链表 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/merge-two-sorted-lists/description/

同理x=tail-k,设x为慢指针slow,tail为快指针fast,转换成k=fast-slow,让fast先行k步,然后fast和slow同行,fast走到空,slow走到k。就是用两个指针作为一个关系式中的变量。


4.合并两个有序链表 

此题思路与链表分割的思路一致。待补充


5.链表分割

链表分割_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId=8&&tqId=11004&rp=2&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking

审题:要求是以某个数为分界分割一个链表。不改变相对位置。

分而治之。这里用到的是链表思想。头插法比较适用于解决自定义新建链表问题,可以根据需求新开辟一个链表,根据需求头插或尾插链表来将符合条件的节点插入。

思路:将>x的链表节点插入一个新建的链表中即做l1。将<=x的链表节点插入另一个新建链表中即做l2.再把l1与l2相互连结上。smalltail连到l2哨兵头节点的下一个节点。这里注意连结是要关注头尾 节点的状态。bigtail最后指向的是cur,这时cur指向null,所以可能会成环。要置空。

class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        ListNode* bigHead,*bigTail,*smallTail,*smallHead;
        bigHead=bigTail=(ListNode*)malloc(sizeof(ListNode));//新建哨兵位的头节点
        smallHead=smallTail=(ListNode*)malloc(sizeof(ListNode));
        ListNode* cur=pHead;
        if (cur==NULL)
            return NULL;
        else if (cur->next==NULL)
            return pHead;//边界情况处理v
       while(cur)
       {
           if(cur->val >=x)
           {
               bigTail->next=cur;
               bigTail=cur;
           }
           else
           {
               smallTail->next=cur;
               smallTail=cur;
           }
           cur=cur->next;
       }
        //连接两个链表
        smallTail->next=bigHead->next;
        bigTail->next=NULL;//防成环处理
        pHead=smallHead->next;
        free(bigHead);
        free(smallHead);
        return pHead;
    }  
};

6.链表的回文结构

给定一个链表判断其是否为回文结构,返回一个bool值。

力扣https://leetcode-cn.com/problems/palindrome-linked-list/

 首先,回文结构是一个对称结构。在数学中我们如何证明对称?对折,取前半段,与后半段比较。取后半段与左半段进行比较。或者从两边往中间比较,是否相等。这就是三种思路。

总体分成       找中点   逆置半段链表   比较值

//取前半段逆置,与后半段比较
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode fast = head, slow = head;
        //cur指向当前要反转的节点,pre指向cur的前一个节点
        ListNode cur = head, pre = null;
        while (fast != null && fast.next != null){
            //查中点
            fast = fast.next.next;
            slow = slow.next;
            //反转链表
            cur.next = pre; //把当前节点的next 指向 前一个节点
            pre = cur;      //前一个节点 赋值为 当前节点
            cur = slow;     //当前节点 赋值为 下一个节点
        }
        //此时链表长度为奇数,应该跳过中心节点
        if (fast != null) slow = slow.next;
        //此时pre指向反转后链表的头,slow指向后半部分链表的头,开始比较
        while (slow != null && pre != null){
            if (slow.val != pre.val){
            	//如果不相等,返回否
                return false;
            }
            slow = slow.next;
            pre = pre.next;
        }
        return true;
    }
}
取后半段逆置,与前半段比较
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode* list1=head;
        ListNode* fast=list1;
        //求中点
        if(fast==NULL)
        return false;
        while(fast!=NULL&&fast->next!=NULL)
        {
             list1=list1->next;
             fast=fast->next->next;
        }
        return list1;
        //中点后头插至新链表
        ListNode* newhead=NULL;
           ListNode* cur=list1->next;
           list1->next=NULL;
        while(cur)
        {
           cur=cur->next;
           cur->next=newhead;
           newhead=cur->next;
        }
       return newhead;
       while(newhead)
       {
           if(head->val!=newhead->val)
           {
               return false;
           }
           newhead=newhead->next;
           head=head->next;
       }
         return true;
    }
};

7.相交链表 160. 相交链表 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/intersection-of-two-linked-lists/description/

题目简介,判断两个链表是否为相交链表,如果是返回相交节点,如果不是,返回空。

 学习启示

1.相交链表的定义:假设如图N-2,从定义上来看,2和2可以同时指向3,但是3却不可以同时指向4,9,因为同一个指针不能指向唯一地址,多个指针可以指向同一地址。所以定义应该是N-1。这里运用了假设法

2.题目包含两层意思,判断和找到节点,所以方法是受限的。

分析:本质上是我们已知相交链表的特征,从而去从不同角度去描述相交链表的定义。而根据时间复杂度和空间复杂度分析出哪种方式更优。一般来说:有多少特征就有多少个角度分析,进而就有多种解法。

a:有相同的next指针。 (两个点的指针指向相同)

 b:最后一个节点相同。(很好获取最后一个节点)

3.判断是否是相交链表,我们可以考虑上述这两个角度:

a:上链中的每个点要对应下链中每个点,从而判断出那两个点是重合的。

故时间复杂度最差是o(N^2).n->n对应关系。方法上看够暴力,但是可优化M-1(最差情况),M-2(一般情况)优化思路:是将最差情况优化成一般的好处理的情况,这是一种处理方式。数学角度就是运用了构造法。

这里让长链先行(短链与长链的差距步)。这与上面的“快慢指针”方法是一致的。

b:分别遍历上链和下链寻找到最后一个节点,再进行一次判断。

时间复杂度稳定的为  O(N)。

从时间复杂度的角度看肯定还是更占优势,但是根据题义最好用优化的方法a.这体现着一种思考方式。网上流传过一句话:“从时间复杂度角度多去分析,200题之后再无普手”。

4.梳理一下,我们大概要完成:

优化M-1到M-2.(快慢指针);找交点(三种情况,无,正常情况,找到了节点)

5.细节处理:链表为空,链表只有一个元素。这个地方并非处理成if else的选择结构,而是处理成了流程逻辑结构:找到了节点,正常情况在找,没找到。但有的题中,就不能将多种情况化成流程逻辑结构,得单独的分情况讨论。算法可见流传的还是C语言按步执行的方法多一些!

 struct ListNode* curA=headA;
    struct ListNode* curB=headB;
    int lenB=0,lenA=0;
    while(curA)
    {
        lenA++;
        curA=curA->next;
    }
    while(curB)
    {
        lenB++;
        curB=curB->next;
    }
    //比较长短
    struct ListNode* longlist=headA;
    struct ListNode* shortlist=headB;
    if(lenB>lenA)
    {
        longlist=headB;
        shortlist=headA;
    }
    //走差的长度
    int tag=abs(lenA-lenB);
    while(tag--)
    {
        longlist=longlist->next;
    }
    //一起走相同的步数,找交点
    while(shortlist&&longlist)
    {
        if(longlist==shortlist)
        return shortlist;
         shortlist=shortlist->next;
         longlist=longlist->next;
    }
      return NULL;
}

 8.判断链表是否有环

141. 环形链表 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/linked-list-cycle/description/

 题目介绍,判断链表中是否有环。

环的特征是什么?

环首先有一个环节点,这个结点有两个next指针指向它。但是用快慢指针的话,就得有两个头,我们就需要断点改变结构。这样的作法就有些小题大作了。

我们发现环结构并不违背语法规则,只是,它使指针变量处于无限循环状态。利用这个循环,我们可以进行挖掘。但这个思路,我觉得从无到有不好证明,跟不容易想。

我们可以通过的手段范围太大,这里我们也可以初步看到双指针的“构造关系”作用。

代码见下题。


9.环形链表 

题目介绍,判断链表是否有环以及找到入口点。

标题

首先分析题干,两层意思,判断与证明。

借鉴于证明相交链表的思路,我们首先观察T1,去挖掘环链表的特征。涌入脑海的是两个指针直线环起点。但是这个比这个特征更明显的是环性链表会造成无限死循环。这两种思路都可以尝试。

由无限死循环可以抽象成循环模型。验证方法是不唯一。可以考虑循环计数,这里就 抽象出了物理的追击模型。与死循环相关的程序,如果能找到等量关系式,都可以用于链表的证明。

除了像分析特征以外还要进一步分析影响特征的因素,这一步的分析将影响我们分析一些特殊情况。进而完善普适性的解法。这里我们可以看T3和T4就是两种状况分析.影响链表的因素有环的大小和环前面的长度,为啥是这两个因素呢??这并非通过肉眼观察或幸运想象猜到的。而是因为我们通过公式推导出结论进而分析出影响因素,画出图。

学习启示:

1.看图T1,我们想用环形链表无限不循环的特征来证明。模型是追击模型中位移关系。通过快慢指针方向灵活和位移二倍的特征来构造关系式。快慢指针在链表的问题中可见有很核心的用处。

2. 看图T3,T2,这是结论的证明过程。

既然入口点是未知数,那么就要列表达式中含入口点关系的等式。所以我们可以把(meet)到入口点的距离设成X,把链表头(head)到入口点的距离设成L。在列表达式的时候,尽量要让情况容易分析。(所以T3是一张简图)快指针的路程是慢指针的二倍。

2*(L+X)=N*C+X+L.解释一下N*C:快指针每走两步,而慢指针只走一步,所以快指针一定是扣圈追上慢指针的。而且两指针的相遇一定在入口点或环上。那有的人可能会问那为啥慢指针就不可能出现N*C的情况?其实快指针追上慢指针一定是慢指针进入环走过路程不超过环长C的。最大值是C-1。快指针每次落下慢指针一步,这一步会随着时间而越积累越多,等这个累积的一步达到C时,他们扣圈相遇了。所以慢指针在环内走一定不会超过C,否则,他就处于相遇状态。也可能是永远追不上的状态。化简原式:L=(N-1)*C+C-X;(N-1)*C可以忽略,X点处相遇,一个指针留在slow,另一个指针返回fast,同时走,再次相遇点即为环节点。

3.验证:我们可以看出在另一种情景下我们的结论也是成立的。 

代码实现:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode*slow,*fast;
        fast=slow=head;
        while(fast!=NULL&&fast->next!=NULL)
        {
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow)
            break;
        }
        //
        if(fast==NULL||fast->next==NULL)//fast走到头说明没有环
        return NULL;
        slow=head;
        while(slow!=fast)
        {
            fast=fast->next;
            slow=slow->next;
        }
        return  slow;
    }
};

解法二:

这种解法就是最先提到的:有共同的下一个节点。把环问题转换成相交链表问题。

首先,我们假定一个快指针追上慢指针的相遇点meet,然后把meet和下一个节点next断开,环变成了相交链表,相交点即为环节点。

实现:meet点对应的条件是,快指针地址==慢指针地址;meet与next断开后,返回next为下链的头节点。   slow,fast返回head,寻找相交节点即为所求。


》方法提炼

A链表思想

头插法:分而治之。链表适合拆分和重组操作所以一些需要分类或局部调整的题目都可以考虑用链表的拆分。

B双指针法

通过双指针充当关系中的变量。即描述关系。

通过双指针构建场景。

后面还会再整理一个双指针的专题。


                                   如有错误,请多多指正!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘敬_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值