LeetCode——141. Linked List Cycle && 142. Linked List Cycle II

  今天再次推出一套组合题。
  首先是:141.Linked List Cycle
  问题描述:

Given a linked list, determine if it has a cycle in it.

Follow up:
Can you solve it without using extra space?

  题目大致的意思就是给你一个链表,判断它里面是否含有环,有的话返回true,没有的话返回false。
  我们可以把这个链表抽象成一条直线加一个圆(如果它有环的情况下),如下图:

  
  
  对于这道题,我一开始的想法就是,保存你走过的点,然后当你再次走到你走过的点,不就可以证明链表有环咯。这种方法很简单,我就没写代码,用一个hashmap来保存点就可以实现了。
  但是,题目要求的是常数级的空间复杂度,说明我们不能通过保存状态的这种方法来实现了,得想另外一种方法。最近刚好要体育测试,我就想起与女朋友跑步的情形:她跑得慢,我跑得快,最后我跑多一圈还是会追上她,与她相遇。放在这里就是:如果有环的话,我用一个快指针(移动比较快的)在前面跑,一个慢指针(移动比较慢的)在后面跑,如果最后快指针能够与慢指针相遇,不就说明链表中有环了吗?问题迎刃而解。我写了两种代码,第一种是两者的速度是会不断变大的,他们之间速度的差距也会不断变大,这样子可以快一点到达环,但是在环中也要走比较多的路两者才能相遇;第二种是固定快指针的速度是慢指针速度的两倍,快指针一次走两步,慢指针一次走一步,这样子虽然比较慢到达环,不过在环中两者会很快就相遇。两者其实差别不大,第二种是我在做第二题的时候才写的,因为第二题就需要具体的量化了,所以必须要有一个准确的量。下面是代码:

    public boolean hasCycle(ListNode head) {
        //方法1,两者的速度差距越来越大,最后也会相遇,不过可能会走了多余的路,效率低一点
        ListNode fastP = head;
        ListNode slowP = head;
        int fastPath = 1;
        int slowPath = 0;
        while(slowP != null) {
            //根据速度来移动快指针
            for(int i = 1; i <= fastPath; i++) {
                fastP = fastP.next;
                if(fastP == null)
                    return false;
                if(fastP == slowP)
                    return true;
            }
            //根据速度来移动慢指针
            for(int i = 1; i <= slowPath; i++) {
                slowP = slowP.next;
                if(slowP == null)
                    return false;
                if(slowP == fastP)
                    return true;
            }
            //因为快指针移动地比慢指针快,因此,快指针所移动的步数一定逐渐多余慢指针,才能显出两者速度的差距
            slowPath += 1;
            fastPath += 2;
        }
        return false;


        //方法2,两者的速度定下来,快指针的速度是满指针的两倍
        //那么实际上只要快指针比满指针走多一圈,两者就可以相遇了,不需要走冤枉路
        ListNode fastP = head;
        ListNode slowP = head;
        while(fastP != null && fastP.next != null) {
            fastP = fastP.next.next;
            slowP = slowP.next;
            if(slowP == fastP)
                return true;
        }
        return false;
    }

  第一题的解法就是这样,其实也不难。


  接下来就是第二道题:142. Linked List Cycle II
  问题描述:

Given a linked list, return the node where the cycle begins. If there is no cycle, return null.

Note: Do not modify the linked list.

Follow up:
Can you solve it without using extra space?

  这道题与上一道题差不多,只不过这道题是要找到环的起点,并返回。如果链表中不存在环就返回null。
  最开始依然是想到用hashmap来保存访问过的点,当你再次访问到已经访问过的点时,那个点就是起点,很容易理解,不过不满足常数级空间复杂度,代码如下:

    public ListNode detectCycle(ListNode head) {
        //这是使用了额外空间的方法
        Map<Integer, List<ListNode>> tempMap = new HashMap<>();
        ListNode cur = head;
        while(cur != null) {
            if(!tempMap.containsKey(cur.val)) {
                List<ListNode> tempList = new LinkedList<>();
                tempList.add(cur);
                tempMap.put(cur.val, tempList);
            }
            else {
                for(ListNode p : tempMap.get(cur.val)) {
                    if(p == cur)
                        return cur;
                }
                tempMap.get(cur.val).add(cur);
            }
            cur = cur.next;
        }
        return null;
    }

  当然了,我们肯定是要继续优化,实现常数级的空间复杂度。不过说实话,挺难想的。后来我也是直接看大神的解答,才发现,这里涉及到了一点简单的几何推导。我是真的没想到啊,写个算法题,简单的几何推导也弄出来了。下面就看我的图来跟着我的思路走吧,其实很简单的:
  
  这里写图片描述
  a是链表起点到环起点Y的距离,Z是快指针与慢指针第一次相遇的点,b是Y到Z的劣弧,c是Z到Y的优弧

  前方高能!!!
  根据快指针的速度是慢指针速度的两倍,有:
  2(a + b) = a + b + c + b
  a+b是慢指针在相同时间内走的距离,a + b + c + b是快指针在相同时间内走的距离
  两边相互消去,可以得到:
  a + b = b + c
  a = c!!!!!!
  这一个等式相当重要。它意味着,用两个相同速度的指针,一个从第一次相遇点出发,另一个从链表起点出发,两者第一次相遇的点,就是环的起点!!!问题不就解决了吗!!!
  下面是代码:

    public ListNode detectCycle(ListNode head) {
        //不使用额外空间的方法,依旧是使用快慢指针,只不过这里限制了快指针的速度为慢指针速度的两倍
        ListNode fastP = head;
        ListNode slowP = head;
        //这个循环是为了获取快指针和满指针的相遇点
        while(true) {
            if(fastP == null || fastP.next == null)
                return null;
            fastP = fastP.next.next;
            slowP = slowP.next;
            if(fastP == slowP)
                break;
        }
        ListNode begin = head;
        //根据公式,从相遇点出发与从链表起点出发,两者最终相遇的地方,就是起点
        while(begin != fastP) {
            begin = begin.next;
            fastP = fastP.next;
        }
        return begin;
    }

  这种解法真是太6了!有没有!我根本就没想到这种题目还能抽象成几何题来做,看来我还是太弱了。
  我觉得吧,以上两道题,都考了我们一种将具体的模型进行抽象的方法。如果在这两道题中,我们能够对这个模型进行抽象,然后运用到几何中一些简单的原理,问题是很容易解决的。所以有时我们需要将抽象的模型具象化,有时也需要将具体的模型抽象化,这也是一种很重要的思维。
  谢谢大家观看我的博客。如果有不明白的地方,或者是文中有错误的地方,欢迎指出,谢谢!如果大家喜欢我的博客,也可以给我点点赞。你们的点赞就是我的动力!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值