力扣234_回文链表

题目描述
请判断一个链表是否为回文链表。

示例 1:
输入: 1->2
输出: false

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

算法思想
方法一:双指针法
我们知道,判断一个数组的回文是非常简单的,只需要一头一尾两个指针,边走边检查即可,但是链表怎么办呢?
直接暴力,把链表存入数组,用数组的方法判断。

class Solution {
    public boolean isPalindrome(ListNode head) {
    	//辅助数组部分 把链表的值全部存入数组
        List<Integer> list = new ArrayList<>();
        ListNode p = head;
        while(p != null) {
            list.add(p.val);
            p = p.next;
        }
        
        //数组查找回文部分
        int front = 0;
        int back = list.size() - 1;
        while(front < back) {
            if(list.get(front) != list.get(back)) {
                return false;
            }
            front++;
            back--;
        }    
        return true;       
    }
}

复杂度分析
时间复杂度:辅助数组部分为O(n),数组查找回文部分为O(n/2),总的来说就是O(n)
空间复杂度:用到一个大小为n的辅助数组,为O(n)

方法二:递归
顺着方法一的思路,我们可以不用辅助数组就利用双指针去遍历吗?
其实,借助二叉树后序遍历的思路,不需要显式反转原始链表也可以倒序遍历链表。
先来看看二叉树后序遍历是怎么写的:

void traverse(TreeNode root) {
    traverse(root.left);
    traverse(root.right);
    // 后序遍历代码
}

其实,树结构不过是链表的衍生,二叉树能实现后序遍历,我们链表也能!

/* 倒序打印单链表中的元素值 */
void traverse(ListNode head) {
    if (head == null) return;
    traverse(head.next);
    // 后序遍历代码
    print(head.val);
}

借助这个思想,right指针借助后序遍历抵达最后的节点,再通过弹出栈访问它的前一个节点(注:这里的递归实际上就是把链表节点放入一个栈,然后再拿出来,这时候元素顺序就是反的,只不过我们利用的是递归函数的堆栈而已。

class Solution {
    ListNode left;
    public boolean isPalindrome(ListNode head) {
        left = head;
        return traverse(head);
        
    }

    public boolean traverse(ListNode right) {
        if(right == null) return true;
        boolean res = traverse(right.next);
        res = res && (left.val == right.val);
        left = left.next;
        return res;
    }
}

递归工作栈如图所示
在这里插入图片描述
复杂度分析
时间复杂度:需要遍历一遍链表,故为O(n)
空间复杂度:利用一个递归工作栈,O(n)

方法三:反转部分链表
方法一二都是O(n)的空间复杂度,我们找寻一下更节省空间的办法。
我们先设想这样的方法,把链表反转过后(参考之前的题目:反转链表I)存入一个新的链表,然后再与目标链表比较,判断是否相同,但这样空间复杂度还是O(n)。
借助这个思路,我们可以仅仅反转链表的一部分吗,这样不就只需要O(1)的空间复杂度了!
也就是说,我们只需要反转链表的后半部分,再与前半部分比较。
到底哪里才是后半部分呢,如果链表节点为奇数,那么中间节点(不包括中间节点)以后的部分就是后半部分,如果链表节点为偶数,那么靠后那个中间节点以后就是后半部分,如图所示
在这里插入图片描述
下面来看具体的反转过程,我们使用快慢指针来找中间节点

//使用快慢指针找到中间值
ListNode slow = head;
ListNode 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;

注意这里的终止条件,很巧妙。
整体代码如下

class Solution {
    public boolean isPalindrome(ListNode head) {
        //使用快慢指针找到中间值
        ListNode slow = head;
        ListNode 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;
    }

    public ListNode reverseList(ListNode head) {
        if(head == null) return null;
        if(head.next==null) return head;
        ListNode last = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }

}

复杂的分析
时间复杂度:需要遍历一遍链表,故为O(n)
空间复杂度:O(1)

相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页