【随想录6 】环形链表与回文链表总结(带正确性证明)

个人认为链表这部分的算法相对简单,笔试中需要ac,面试需要能说明白为什么这么操作能保证其结果正确性就好了。

链表问题考察的时间复杂度,空间复杂度稍不重要,笔试中为了过怎么都可以,面试一定要聊时间空间都最优的解法,

141. 环形链表

142. 环形链表 II

234. 回文链表

环形链表

141. 环形链表

法一,用set将每个节点装进去,如果下次遇到同样地址的节点,那就代表有环,如果直到遍历的指针都为空了,还没有遇到重复的节点,那就是没有环了。注意set装的是节点的地址不是节点的值,值可以重复,但是地址值是唯一的。

法二,使用双指针,slow一次一步,fast 一次两步,如果fast遇到了null,那就是没有环。
如果fast == slow 了,那就是代表他们在环里相遇了,也就是存在环。

快慢指针正确性证明

让你求个证明,总不能来个显然成立吧?(狗头

假设该链表在环出现之前有L个结点,环中有C个结点。
再假设慢指针初始化时指向的位置为a、快指针初始化时指向的位置为b,
如果二者在 t 次移动后相遇,也就是说:
(a+t-L) mod C == (b + 2 * - L ) mod C,
所以无论a、b的起始位置如何,二者总是会相遇的。
推导:
( a + t - L ) mod C == (b+2 * t-L) mod C
a+t-L-m * C == b+2 * t-L -n * C
t == (a-b) - (m - n ) * C
a=b==> t == ( n - m ) * C;
在保证m和n为正整数的情况下,上式t有正整数解。
若两个指针的步长分别为: x和y (即每走一步步长分别为: x * t 和 y * t )
( x - y ) * t== (a-b)- (m-n)*C
要是程序不陷入死循环必须满足.上面这个等式有正整数解。
比方说:假设链表是由4个结点首尾相接构成的一个圆圈(编号为0~3)
慢指针初始位置在0,每次前进1步;
快指针初始位置在3,每次前进3步;
有: (3-1) *t==(3-0)- (m-n) *4
此等式没有正整数解,所以虽然这两个指针也是- -快- -慢一前一后,但它们永远也不会相
遇!

若快慢都在 0位置,
则有(0-0) *t==(0-0)- (m-n) *4
此时,是有整数解的,所以验证是否有环与快慢指针的初始位置有关,与快慢指针之间相差几步是没有关系的,总能找到一个对应的整数,使得其成立。

验证代码:
假设现在是

链表为 1-2-3-4-5-6 6->3 存在一个环
慢指针一次一步,快指针一次三步,初始位置都在head位置

 public static boolean hasCycleTest(ListNode head) {
        if(head==null || head.next==null) return false;
        ListNode slow = head;
        ListNode fast = head;

        while ( fast.next!=null && fast.next.next!=null && fast.next.next.next!=null){

            System.out.println("slow.val="+slow.val+"   fast.val="+fast.val);
            slow=slow.next;
            fast=fast.next.next.next;
            if(slow==fast){
                System.out.println("slow.val="+slow.val+"   fast.val="+fast.val);
                return true;
            }
        }
        return false;
    }


    public static void main( String[] args ) {
        ListNode node1  = new ListNode(1);
        ListNode node2  = new ListNode(2);
        ListNode node3  = new ListNode(3);
        ListNode node4  = new ListNode(4);
        ListNode node5  = new ListNode(5);
        ListNode node6 = new ListNode(6);
        node1.next=node2;
        node2.next=node3;
        node3.next=node4;
        node4.next=node5;
        node5.next=node6;
        node6.next=node3;
        System.out.println( hasCycleTest( node1 ) );
    }
//输入如下
slow.val=1   fast.val=1
slow.val=2   fast.val=4
slow.val=3   fast.val=3
true

也可以ac

原题代码:

       public boolean hasCycle(ListNode head) {
        if(head==null || head.next==null) return false;
        ListNode slow = head;
        ListNode fast = head;

        while ( fast.next!=null && fast.next.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast)return true;
        }
        return false;
    }

环形链表 II

142. 环形链表 II

法一,同上,先set判断,然后记录重复的节点,然后再从头遍历找到这个节点返回

法二,快慢指针,slow一次一步,fast一次两步,

快慢指针正确性证明

图源见水印

L=N*C-X
也就是说,head与相遇点的指针同时向后移动,相遇时,就是环的入口。

代码:

    public ListNode detectCycle(ListNode head) {
        if ( head==null || head.next==null)return null;
        ListNode slow = head;
        ListNode fast = head;
        ListNode node = null;

        while ( fast.next!=null && fast.next.next!=null ){
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast){
                node = slow;
                break;
            }
        }
        if(node != null){
            ListNode temp = head;
            while ( temp!=node ){
                temp=temp.next;
                node=node.next;
            }
            return temp;
        }
        return null;
    }

回文链表

234. 回文链表

法一: 遍历节点全部放数组,然后判断
法二: 快慢指针找到中点,慢指针遍历过程中将节点压栈,将剩余的一半与栈顶元素依次比较,如果不想等返回false,直到栈空都没问题返回true
法三: 快慢指针找到中点,然后将中点之后的元素逆置,中点的next指向null,然后head 与 逆置部分后的头节点依次比较,如果直到null,每个元素都相等,则是回文链表。同时判断完之后需要将链表逆置回去。需要注意链表节点为单数与双数的处理。

法三代码:


    /**
     * [234. 回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/)
     *
     * @param head
     * @return
     */
    public boolean isPalindrome( ListNode head ) {
        if ( head.next == null ) {
            return true;
        }
        ListNode slow = head;
        ListNode fast = head;
        while ( fast.next != null && fast.next.next != null ) {
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode reverseStart = slow;
        ListNode node = head;
        // 如果是奇数个节点,此时fast在最后一个节点
        // 可以用fast的下一个节点是否为空来判断整个链表节点个数是技术还是偶数
        if ( fast.next == null ) {
            // 需要将slow 指向下一个节点 再进行翻转
            slow = slow.next;
            ListNode temp = reverseList( slow);
            while ( temp!=null ){
                if(temp.val!=node.val){
                    // System.out.println(temp.val +"-"+ node.val);
                    return false;
                }
                temp=temp.next;
                node=node.next;
            }
            reverseStart.next = reverseList( slow );
        } else {
            // 若节点个数为偶数,则slow 在上中点
            // 需要返回以下中点为首的 翻转后的链表
            //返回 slow.next后半部分链表
            ListNode temp = reverseList( slow.next );
            while ( temp!=null ){
                if(temp.val!=node.val){
                    return false;
                }
                temp=temp.next;
                node=node.next;
            }
            reverseStart.next = reverseList( slow );
        }

        return true;
    }


    public ListNode reverseList( ListNode head ) {
        if ( head == null ) {
            return head;
        }
        ListNode pre = null;
        ListNode cur = head;
        while ( cur != null ) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }

代码更少的解法:


    /**
     * 优秀的解法
     * https://labuladong.gitee.io/algo/2/17/19/
     * @param head
     * @return
     */
    boolean isPalindromeByLa(ListNode head) {
        ListNode slow, fast;
        slow = fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        if (fast != null)
            slow = slow.next;

        ListNode left = head;
        ListNode right = reverseList(slow);
        while (right != null) {
            if (left.val != right.val)
                return false;
            left = left.next;
            right = right.next;
        }

        return true;
    }

参考

如何判断回文链表
142.环形链表II
环形链表的快慢指针相遇的数学证明
环形链表问题(详细证明过程)
有环链表问题及其证明
链表是否有环 快指针走三步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值