• LeetCode--链表2-双指针问题


    LeetCode--链表2-双指针问题

    思考问题:
    判断一个链表是否有环
    列举几种情况:

    graph LR
    A-->B
    B-->C
    C-->D
    D-->E
    E-->C
    
    graph LR
    A-->B
    B-->A
    

    你可能已经使用哈希表提出了解决方案。但是,使用双指针技巧有一个更有效的解决方案。在阅读接下来的内容之前,试着自己仔细考虑一下。

    想象一下,有两个速度不同的跑步者。如果他们在直路上行驶,快跑者将首先到达目的地。但是,如果它们在圆形跑道上跑步,那么快跑者如果继续跑步就会追上慢跑者。

    这正是我们在链表中使用两个速度不同的指针时会遇到的情况:

    如果没有环,快指针将停在链表的末尾。
    如果有环,快指针最终将与慢指针相遇。
    

    所以剩下的问题是:

    这两个指针的适当速度应该是多少?
    

    一个安全的选择是每次移动慢指针一步,而移动快指针两步。每一次迭代,快速指针将额外移动一步。如果环的长度为 M,经过 M 次迭代后,快指针肯定会多绕环一周,并赶上慢指针。

    那其他选择呢?它们有用吗?它们会更高效吗?
    

    题1 环形链表1(简单)

    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode(int x) : val(x), next(NULL) {}
     * };
     */
    class Solution {
    public:
        bool hasCycle(ListNode *head) {
            // 当链表中没有节点或只有一个节点时 false
            if(!head || !head->next)
                return false;
            // 两个节点的初始值都是head
            ListNode* p1 = head;
            ListNode* p2 = head;
            // 
            while( p1->next && p2->next)
            {
                if(p1->next == p2->next->next)
                    return true;
                p1 = p1->next;
                p2 = p2->next->next;
                if( !p1 || !p2 )
                    return false;
            }
            return false;
        }
    };
    

    题2 环形链表2(中等题)

    自己的思路:
    巴拉巴拉
    然而
    自己的思路并不对...
    看看人家的想法好了

    graph LR
    0-->1
    1-->2
    2-->3
    3-->4
    4-->5
    5-->2
    

    剑指offer上这道题的思路,主要就是运用双指针,起点不同。
    设环内节点个数为n,那就一个从0节点出发,另一个从第n+1个节点出发。
    相遇处,就是入口处。
    说白了就是带环的相遇问题。

    所以这道题需要解决几个问题

    1. 确定链表是否有环
    2. 确定链表内节点个数
    3. 确定入口节点
    /**
     * 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) {
            int num = counter(head);
            if ( num == 0)
                return NULL;
            ListNode* pa = head;
            ListNode* pb = head;
            for (int i = 0 ; i < num; i++)
            {
                pb = pb->next;
            }
            while (pa->next && pa->next)
            {
                if(pa == pb)
                    return pb;
                pa = pa->next;
                pb = pb->next;
            }
            return NULL;
        }
        
        int counter( ListNode* head )
        {
            // 链表为空或链表中只有一个节点-->不存在环-返回0
            if( !head || !head->next )
                return 0;
            // 设置双指针
            ListNode* p1 = head;
            ListNode* p2 = head;
            // 
            int count = 0;
            while( p1->next && p2->next )
            {
                // 若p1和P2即将相遇,重新赋值,并开始计数
                if( p1->next == p2->next->next)
                {
                    p1 = p1->next;
                    p2 = p1->next;
                    count = 2;
                    while(p2->next)
                    {
                        if( p1 == p2 )
                        {
                            return count;
                        }
                        p2 = p2->next;
                        count ++;
                    }
                }
                p1 = p1->next ;
                p2 = p2->next->next;
                if(!p1||!p2)
                    return 0;
            }
            return 0;
        }
    };
    
    

    超出时间限制-
    修改为下面的代码,通过了测试;

    修改内容:
    1. 避免了双重的while循环
    2. 避免while的循环的终止条件是真值
    /**
     * 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) {
            // 确定快慢指针相遇的节点
            ListNode* pmeet = meeting(head);
            if ( pmeet == NULL)
                return NULL;
            // 确定环内节点的个数
            int count = 1 ;
            ListNode* p1 = pmeet;
            while( p1->next != pmeet )
            {
                p1 = p1->next;
                count ++;
            }
            // 确定环的入口节点
            ListNode* pa = head;
            ListNode* pb = head;
            for (int i = 0 ; i < count; i++)
            {
                pb = pb->next;
            }
            while ( pa != pb )
            {
                pa = pa->next;
                pb = pb->next;
            }
            return pa;
        }
        
        // 确定快慢指针相遇的节点
        ListNode* meeting (ListNode* head )
        {
            // 链表为空或链表中只有一个节点-->不存在环-返回0
            if( !head || !head->next )
                return NULL;
            // 设置双指针
            ListNode* p1 = head;
            ListNode* p2 = head;
            // 
            ListNode* meet = head;
            while( p1->next && p2->next )
            {
                // 若p1和P2即将相遇,重新赋值,并开始计数
                if( p1->next == p2->next->next)
                {
                    meet = p1->next;
                    return meet;
                }
                p1 = p1->next ;
                p2 = p2->next->next;
                if(!p1||!p2)
                    return NULL;
            }
            return NULL;
        }
    };
    

    题3 相交链表

    graph LR
    A-->B
    B-->C
    C-->F
    E-->F
    D-->E
    F-->G
    G-->H
    
    /**
     * 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 || !headB )
                return NULL;
            // 两个链表从头节点就相交了
            if( headA == headB )
                return headA;
            ListNode* pa = headA;
            ListNode* pb = headB;
            // 求两个链表的长度
            int numa = counter(headA);
            int numb = counter(headB);
            // 哪一个链表长,其指针就往前步进长度差的步长
            int step = 0;
            if ( numa >= numb )
            {
                step = numa - numb;
                for(int i = 0; i < step ; ++i)
                {
                    pa = pa->next;        
                }
            }
            else
            {
                step = numb - numa;
                for(int j = 0 ; j < step; ++j)
                {
                    pb = pb->next;
                }
            }
            
            // 定位第一个相同的节点
            while ( pa && pb &&  (pa != pb) )
            {
                pa = pa->next;
                pb = pb->next;
            }
            return pb;
            
            // 第二种循环的写法
            /*
            while ( pa && pb )
            {
                if ( pa == pb )
                    return pa;
                pa = pa->next;
                pb = pb->next;
            }
            return NULL;
            */
        }
        // 返回单链表中的节点数
        int counter(ListNode* head)
        {
            ListNode* p = head;
            int count = 1;
            if( !p )
                return 0;
            if( !p->next )
                return 1;
            while( p->next )
            {
                p = p->next;
                ++count;
            }
            return count;
        }
    };
    

    题4 删除链表中的倒数第n个节点

    第一想法就是通过辅助栈求解

    /**
     * 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) {
            // 
            if( !head || n <= 0 )
                return NULL;
            // 建立一个辅助栈
            stack<ListNode*> nodes;
            // 遍历链表,依次放入栈中
            ListNode* p = head;
            while(p)
            {
                nodes.push(p);
                p = p->next;
            }
            
            if(n == 1)
            {
                nodes.pop();
                ListNode* pend = nodes.top();
                pend->next = nullptr;
                return head;
            }
            // 遍历栈中的节点到第n-1个节点
            int i = 1;
            while ( i != n-1 && n > 1 )
            {
                if(nodes.empty())
                    return NULL;
                nodes.pop();
                ++i;
            }
            ListNode* pe = nodes.top();
            nodes.pop();
            nodes.pop();
            ListNode* ps = nodes.top();
            ps->next = pe;
            return head;
        }
    };
    

    测试用例通过,但是提交解答报错

    Line 152: Char 17: runtime error: reference binding to misaligned address 0xbebebebebebec0b6 for type 'struct ListNode *', which requires 8 byte alignment (stl_deque.h)
    

    修改后代码如下:

    /**
     * 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) {
            // 
            if( !head || n <= 0 )
                return NULL;
            // 建立一个辅助栈
            stack<ListNode*> nodes;
            // 遍历链表,依次放入栈中
            ListNode* p = head;
            while(p)
            {
                nodes.push(p);
                p = p->next;
            }
            // 倒数第1个节点
            if(n == 1)
            {
                nodes.pop();
                if(nodes.empty())
                    return NULL;
                ListNode* pend = nodes.top();
                pend->next = NULL;
                return head;
            }
            // 倒数n-1个节点之前的节点出栈
            int i = 1;
            while ( i != n-1 && n >= 2 )
            {
                if(nodes.empty())
                    return NULL;
                nodes.pop();
                ++i;
            }
            // 得到第n-1个节点,使其出栈
            ListNode* pe = nodes.top();
            nodes.pop();
            // 第n个节点出栈
            nodes.pop();
            // 如果倒数第n个节点之前再无节点,head = pe
            if(nodes.empty())
            {
                head = pe;
                return head;
            }
            ListNode* ps = nodes.top();
            ps->next = pe;
            return head;
        }
    };
    

    使用辅助栈的时候,代码的鲁棒性要十分注意
    出栈后,栈是否为空一定要注意!!!

    小结 - 链表中的双指针问题

    代码模板:

    // Initialize slow & fast pointers
    ListNode* slow = head;
    ListNode* fast = head;
    /**
     * Change this condition to fit specific problem.
     * Attention: remember to avoid null-pointer error
     **/
    while (slow && fast && fast->next) {
        slow = slow->next;          // move slow pointer one step each time
        fast = fast->next->next;    // move fast pointer two steps each time
        if (slow == fast) {         // change this condition to fit specific problem
            return true;
        }
    }
    return false;   // change return value to fit specific problem
    

    提示

    它与我们在数组中学到的内容类似。但它可能更棘手而且更容易出错。你应该注意以下几点:

    1. 在调用 next 字段之前,始终检查节点是否为空。
      获取空节点的下一个节点将导致空指针错误。例如,在我们运行 fast = fast.next.next 之前,需要检查 fast 和 fast.next 不为空。

    2. 仔细定义循环的结束条件。运行几个示例,以确保你的结束条件不会导致无限循环。在定义结束条件时,你必须考虑我们的第一点提示。

    复杂度分析

    空间复杂度分析容易。如果只使用指针,而不使用任何其他额外的空间,那么空间复杂度将是 O(1)。但是,时间复杂度的分析比较困难。为了得到答案,我们需要分析运行循环的次数。

    在前面的查找循环示例中,假设我们每次移动较快的指针 2 步,每次移动较慢的指针 1 步。

    如果没有循环,快指针需要 N/2 次才能到达链表的末尾,其中 N 是链表的长度。
    如果存在循环,则快指针需要 M 次才能赶上慢指针,其中 M 是列表中循环的长度。
    

    显然,M <= N 。所以我们将循环运行 N 次。对于每次循环,我们只需要常量级的时间。因此,该算法的时间复杂度总共为 O(N)。

    自己分析其他问题以提高分析能力。别忘了考虑不同的条件。如果很难对所有情况进行分析,请考虑最糟糕的情况。

    干啥啥不行,吃饭第一名
  • 相关阅读:
    linux(6)查看进程ps命令
    Python 基础03 序列
    Python 基础02 基本数据类型
    Python基础01 Hello World!
    Linux vi/vim
    Laravel 的HTTP控制器
    Laravel 下的伪造跨站请求保护 CSRF#
    Linux 磁盘管理
    Linux 用户he用户组管理
    Linxu 用户和用户组管理1
  • 原文地址:https://www.cnblogs.com/jiangxinyu1/p/12285012.html
Copyright © 2020-2023  润新知