[Leetcode] 234. 回文链表

题目

leetcode 234. 回文链表

在这里插入图片描述

题解

解法一: 还是跟上道题差不多,可以使用数组(可变数组),把链表的节点的值存入数组,然后使用双指针判断,一个从左到中间,另一个从右到中间。

   public static boolean isPalindrome(ListNode head) {
    
    
        ArrayList<Integer> list = new ArrayList<>();
        // 把链表的值复制到数组中
        ListNode cur = head;
        while(cur != null) {
    
     // O(n)
            list.add(cur.val);
            cur = cur.next;
        }
        // 双指针法判断回文
        int first = 0;
        int last = list.size() - 1;
        while(first < last) {
    
     // O(n/2)
            // 这里犯了一个错误,我用了!=来比较,比较的是地址,这涉及到Integer底层,
            // 对于Integer,会先创建一个池,里面存储-128到127的值,如果值在该范围中,那么就从该池直接拿取,
            // 所以如果创建两个值相同并且在该范围的话,那么它们的地址是一样的。
            // 所以下面得使用equal比较,因为比较的是内容
            if(!list.get(first).equals(list.get(last))) {
    
    
                return false;
            }
            first++;
            last--;
        }
        return true;
    }

复杂度:

  • 时间复杂度:每次访问需要O(1),一个需要访问N个元素,所以时间复杂度为O(N)

  • 空间复杂度:使用了一个可变数组来存储,一共有N个元素,虽然ArrayList有扩容机制(每次扩容为原来长度的1.5倍)但是可以不计,所以空间复杂度为O(N)。


解法二: 还可以使用栈,将链表的每一个节点压入栈,弹出,形成新链表,然后跟旧链表的每一个节点的值一一比较。

复杂度同上。


解法三: 在第一种解法中,使用了N个空间,其实还可以优化,前面学到的反转链表,但是反转整条链表,结果会错误,原因是会改变了原来的链表,所以就比较就会出错,除非创建一条链表的副本,然后再反转再比较。但是可以这样:反转后半段链表,然后跟前半段比较,最后最好把反转的链表接回去。算法实现步骤:

  • 步骤一:先找出前半段链表的尾节点
    • 实现一:计算节点个数,然后根据节点个数遍历到中间节点。(需要遍历2次)
    • 实现二:使用快慢指针寻找中间节点,慢指针每次走一步,快指针每次走两步,只要当快指针的next遇到null并且快指针的next的next遇到null就表示找到。(需要遍历1次)
  • 步骤二:然后就可以反转后半段的链表。
  • 步骤三:前后半段一一比较。
  • 步骤四:结束时把后半段链表反转回去。虽然不反转不造成影响。
   public static boolean isPalindrome(ListNode head) {
    
    
        if(head == null) {
    
    
            return true;
        }
        // 1.前半段的尾节点
        ListNode firstHalfEndNode = endOfFirstHalf(head);
        // 2.从前半段的尾节点的下一个节点开始反转链表
        ListNode secondHalfStart = reverseLink(firstHalfEndNode.next);

        // 3. 比较
        ListNode firstHalfNode = head;
        ListNode secondHalfNode = secondHalfStart;
        boolean result = true; // 本来不需要这个,可以直接在循环中直接返回,但是最后还得把后半段链表反转回来
        while(result && secondHalfNode != null) {
    
     // 刚开始用 firstHalf != firstHalfEndNode 出错了,原因是在这个位置还得比较,画画图就懂
            if(firstHalfNode.val != secondHalfNode.val) {
    
    
                result = false;
            }
            firstHalfNode = firstHalfNode.next;
            secondHalfNode = secondHalfNode.next;
        }
        // 4.反转回来
        firstHalfEndNode.next = reverseLink(secondHalfStart);
        return result;
    }

    private static ListNode reverseLink(ListNode secondHalfStart) {
    
    
        ListNode cur = secondHalfStart;
        ListNode tempNode = null;
        ListNode newNode = null;
        while(cur != null) {
    
    
            // 先把cur的next暂存
            tempNode = cur.next;
            // 然后把cur的next指向新链表
            cur.next = newNode;
            // 在新链表中把头指针指向cur
            newNode = cur;
            // 归还
            cur = tempNode;
        }
        return newNode;
    }

    private static ListNode endOfFirstHalf(ListNode head) {
    
    
        ListNode slow = head;
        ListNode fast = head;
        while(fast.next != null && fast.next.next != null) {
    
    
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

图解:(如果动态图太快,就截图看)

在这里插入图片描述

复杂度:

  • 时间复杂度:O(N):一共需要遍历N个节点。
  • 空间复杂度:O(1):使用的是常数时间的额外空间,都记为O(1)。

这种算法的缺点是,在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执执行过程中链表暂时断开。

猜你喜欢

转载自blog.csdn.net/weixin_41800884/article/details/107625468
今日推荐