• LeetCode 234.回文链表


    题目传送门:回文链表


    请判断一个链表是否为回文链表。

    示例1:

    输入: 1->2
    输出: false
    

    示例2:

    输入: 1->2->2->1
    输出: true
    

    进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?


    方法一:将链表的值复制到数组中然后使用双指针法

    该方法使用了数组的特性,因为数组查找起来非常简单,而链表只能从前到后查找。首先我们将链表的值复制到数组中,然后使用头尾两个指针,两个指针都往数组中间遍历,如果值都相等的话则链表为回文链表。

    • 时间复杂度:O(n),其中 n 指的是链表的元素个数。
      • 第一步:遍历链表并将值复制到数组中,O(n)
      • 第二步:双指针判断是否为回文,执行了 O(n/2) 次的判断,即 O(n)
      • 总的时间复杂度:O(2n) = O(n)
    • 空间复杂度:O(n),其中 n 指的是链表的元素个数,我们使用了一个数组列表存放链表的元素值。
    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode(int x) { val = x; }
     * }
     */
    class Solution {
        public boolean isPalindrome(ListNode head) {
            //1.新建ArrayList
            List<Integer> vals = new ArrayList<>();
    
            //把LinkedList的值赋给ArratList
            while(head != null) {
                vals.add(head.val);
                head = head.next;
            }
    
            //分别创建头尾两个指针,然后比较两个指针节点的值
            //要注意这里为包装类,不能使用!=来进行比较,需要使用! .equals()来做判断
            int front = 0;
            int back = vals.size() - 1;
            while(front < back) {
                if(!vals.get(front).equals(vals.get(back))) {
                    return false;
                }
                front++;
                back--;
            }
            return true;
        }
    }
    

    方法二:切成两半,把后半段反转,然后比较两半是否相等。

    因为本题的进阶要求为使用 O(1) 的空间复杂度,所以我们不能使用额外的空间,那么,改怎么去解决这个问题呢?

    避免使用 O(n) 额外空间的方法就是改变输入。

    我们可以将链表的后半部分反转(修改链表结构),然后比较两半是否相等。

    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode(int x) { val = x; }
     * }
     */
    class Solution {
        // 在这个算法中,如果链表为奇数个节点,那么最后比较中经过反转后的链表要比前半段多一个最中间的节点
        public boolean isPalindrome(ListNode head) {
            if(head == null || head.next == null) return true;
            ListNode slow = head;
            ListNode fast = head.next;
            while(fast != null && fast.next != null) {
                slow = slow.next;
                fast = fast.next.next;
            }
            if(fast != null) slow = slow.next; // 如果链表为偶数,则将slow移动到中间位置的下一个节点
            cut(head, slow); // 分割链表
            return isEqual(head, reverse(slow)); // 反转链表的后半段并返回结果
        }
    
        // 切断链表方法
        private void cut(ListNode head, ListNode cutNode) {
            while(head.next != cutNode) {
                head = head.next;
            }
            head.next = null;
        }
    
        // 反转链表方法
        private ListNode reverse(ListNode head) {
            ListNode newNode = null;
    
            while(head != null) {
                ListNode nextNode = head.next;
                head.next = newNode;
                newNode = head;
                head = nextNode;
            }
            return newNode;
        }
    
        // 比较两个链表的值是否相等的方法
        private boolean isEqual(ListNode l1, ListNode l2) {
            while(l1 != null && l2 != null) {
                if(l1.val != l2.val) return false;
                l1 = l1.next;
                l2 = l2.next;
            }
            return true;
        }
    }
    

    自己遇到的坑:我在用第二种方法做这个题的时候遇到了一个很大的坑,找了好久,最终还是写了测试程序 debug 之后,才意识到自己的问题。在第一次写第二种方法的时候 cut 方法中的 while 循环中的判断条件我写的是 head != cutNode ,发现测试算法最后提交时是错误的,通过不了,改成 head.next != cutNode 之后就成功了。没办法,自己空想想不出来,就只好去 debug 一步步看程序的运行结果,在好几遍的 debug 之后,我终于发现了错误原因。因为算法的 cut 方法的目标为切断链表,把链表一分为二后再来对后半段进行翻转和比较的操作。我们的目标为把从链表头部到 slow 指针前面的一个节点和从 slow 到尾节点切开。所以当 head != slow 的话,每次 slow 都会被切断(切的位置在 slow 后面),保留 slow 节点的这个值,所以算法出错。

    举个例子:如果测试用例为 [1,0,1],是一个回文链表。如果 cut 方法中 while 的判断条件为 head != slow 的话,经过上面的指针移动 slow 会移动到链表中 0 这个节点。因为判断条件会使 head 移动到 slow 指针这个位置,所以下一步 head.next = null 的时候链表会在 0 这个节点后面切一刀。但是这一刀会影响 slow 这个将要被拿去翻转的链表,也就是 slow 这个链表也会被切断,变为只有一个 0 节点,之后的比较中就会出错。

    但是判断条件如果为 head.next != cutNode 的话,切点就在 slow 指针前面,slow 这个后半段链表就不会受影响,而且当链表为奇数个节点时 slow 链表也会多出一个中间结点,但这并不影响后面的比较,所以这样写才是正确的。

    如果博客内容有误,请联系我修改指正,非常感谢! 如果觉得这篇博客对你有用的话,就帮我点个小小的赞吧! 一起加油鸭, 越努力,越幸运!!!
  • 相关阅读:
    Android开源项目分类汇总
    android开发之Tabhost刷新
    android开发之图表
    通过易宝实现网上支付
    HTTP协议
    汽车美容管理系统质量属性
    框架选择的原因及其说明
    浅析软件架构
    《需求分析与系统设计》读书笔记3
    《需求分析与系统设计》读书笔记2
  • 原文地址:https://www.cnblogs.com/studywithme/p/13572491.html
Copyright © 2020-2023  润新知