• 关于链表的面试问题(判断一个单链表中是否有环)


    上个月去CVTE面试安卓工程师时,面试官问了一道关于链表的算法问题,判断一个单链表中是否有环,当时我没仔细思考,没考虑到可能有子环的。

    首先链表结点声明如下:

    struct ListNode
    {
        int key;
        ListNode * next;
    };

    思路:如果一个单链表中有环,用一个指针去遍历,永远不会结束,所以可以用两个指针,一个指针一次走一步,另一个指针一次走两步,如果存在环,则这两个指针会在环内相遇,时间复杂度为O(n)。

    bool HasCircle(ListNode * pHead)
    {
        ListNode * pFast = pHead; // 快指针每次前进两步
        ListNode * pSlow = pHead; // 慢指针每次前进一步
        while(pFast != NULL && pFast->next != NULL)
        {
            pFast = pFast->next->next;
            pSlow = pSlow->next;
            if(pSlow == pFast) // 相遇,存在环
                return true;
        }
        return false;
    }

    用java试下,因为java是没有指针的,所以需要改动一下。

    /*
     *单链表的结点类
     */
    class LNode{
        //为了简化访问单链表,结点中的数据项的访问权限都设为public
        public int data;
        public LNode next;
    }
    public class LinkListUtli {
        public static boolean hasCircle(LNode L)
        {
            LNode slow=L;//slow表示从头结点开始每次往后走一步的指针
            LNode fast=L;//fast表示从头结点开始每次往后走两步的指针
            while(fast!=null && fast.next!=null) 
            {
                if(slow==fast) return true;//p与q相等,单链表有环
                slow=slow.next;
                fast=fast.next.next;
            }
            return false;
        }
    }

    拓展问题1:如果单链表有环,找出环的入口节点(环的连接点)。

    这里先证明一个定理:碰撞点到连接点的距离=头指针到连接点的距离

    假设单链表的总长度为L,头结点到环入口的距离为a,环入口到快慢指针相遇的结点距离为x,环的长度为r,慢指针总共走了s步,则快指针走了2s步。另外,快指针要追上慢指针的话快指针至少要在环里面转了一圈多(假设转了n圈加x的距离),得到以下关系:
        s = a + x
        2s = a + nr + x
        =>a + x = nr
        =>a = nr - x

    由上式可知:若在头结点和相遇结点分别设一指针,同步(单步)前进,则最后一定相遇在环入口结点。

    public class LinkListUtli {
        //当单链表中没有环时返回null,有环时返回环的入口结点
        public static LNode searchEntranceNode(LNode L)
        {
            LNode slow=L;//p表示从头结点开始每次往后走一步的指针
            LNode fast=L;//q表示从头结点开始每次往后走两步的指针
            while(fast !=null && fast.next !=null) 
            {
                if(slow==fast) break;//p与q相等,单链表有环
                slow=slow.next;
                fast=fast.next.next;
            }
            if(fast==null || fast.next==null) return null;
    
            slow=L;
            while(slow!=fast)
            {
                slow=slow.next;
                fast=fast.next;
            }
            return slow;
        }
    }

    拓展问题2:求环的长度。

    让指针指向入口节点,遍历直到回到入口节点,走过的长度即环的长度。

            //求单链表环的长度
        public static int circleLength(LNode L)
        {
            LNode p=searchEntranceNode(L);//找到环的入口结点
            if(p==null) return 0;//不存在环时,返回0
            LNode q=p.next;
            int length=1;
            while(p!=q)
            {
                length++;
                q=q.next;
            }
            return length;//返回环的长度
        }

    思维扩展:这里用到了快慢指针来判断单链表是否有环,快慢指针还能快速解决其他链表问题。

    一:已知单链表的头指针,查找到倒数第K个节点

    这道题的通俗的解法就是先遍历一边链表,得到链表的长度N,然后再从头开始遍历N-K个节点即可

    但是现在如果要求只遍历一遍链表的话,该怎么操作呢?

    这时候就可以借助快指针和慢指针了

    我们定义一个快指针P和慢指针Q,先让P指针走到K个节点位置,然后Q指针从头指针开始和P一起移动,当P移动到尾部的时候,那么此时Q节点所在的位置就是倒数第K个节点。

    二:已知单链表的头结点,查找到链表的中间节点

    这道题的通俗的解法和上面的方法一样,就是先遍历一边链表,得到链表的长度N,然后再次遍历N/2个节点即可

    但是现在同样的如果要求之遍历一边链表的话,该怎么操作呢?

    这时候我们同样可以借助快指针和慢指针了

    我们定义一个快指针P和慢指针Q,P和Q同时从头指针出发,快指针P每次移动两步,慢指针每次移动一步,当快指针P到尾部的时候,慢指针Q所在的位置就是中间节点的位置。

  • 相关阅读:
    表达式计算 六月飞雪
    code::blocks 单步执行 六月飞雪
    5.1 字符串 六月飞雪
    对使用倒序的一维数组解决0/1背包问题的理解 六月飞雪
    5.2 高精度运算 六月飞雪
    关于ArcEngine“不能再打开其他表了”的错误 (20121026 15:43:33)
    关于AO插入对象
    多线程使用实例
    C#程序运行时间长出现无法响应状态
    Geographic coordinate system和projected coordinate
  • 原文地址:https://www.cnblogs.com/ghimtim/p/4882916.html
Copyright © 2020-2023  润新知