判断list是否为空_判断一个链表是否是回文结构


题目:判断一个链表是否为回文结构

给定一个链表的头节点head,请判断该链表是否为回文结构。

例如:1 ->2 -> 1,返回true。1 -> 2 -> 2 -> 1,返回true。15 -> 6 -> 15,返回true。1 -> 2 -> 3,返回 false 。

链表结构:

class Node{
    int value;
    Node next;

    public Node(int val){
        this.value = val;
    }
}

进阶:如果链表长度为 N ,时间复杂度达到O(N) ,额外空间复杂度达到 O(1)。

法一:借助辅助空间,使用栈

思路:

将链表 List 节点依次压入栈中,再依次弹出节点 node ;将 List 从头节点 head 开始比较,一旦发现不同则返回true;当head 为空时【比较结束】返回true。

3d1914f8dcc46a11ab531bc99355da48.png

图1 使用栈的思路

Code:

public boolean isPalindrome(Node head){
    Stack<Node> stack = new Stack<Node>();
    Node cur = head;
    while(cur != null){ //压栈
        stack.push(cur);
        cur = cur.next;
    }
    while(head != null){
        if(head.value != stack.pop().value)
            return false;
        head = head.next;
    }
    return true;
}

时间复杂度:需要遍历整个 List ,所以 T(n) = O(n) 。

空间复杂度:由于需要保存 List 的所有节点,所以 S(n) = O(n) 。

法二、快慢指针法

看到这个标题,思考一下能否不用保存整个 List 去解决这道题?

根据回文的特性,回文 List 必然是关于中间的对称轴对称的,如果从中间开始比较,是否可以用原先一半的空间来解决它? ——Yes.

思路:

  1. 快指针 A 一次走 2 步,慢指针 B 一次走 1 步;当 A 走到 List 末尾时, B 刚好到中点;
  2. B 将后半部分遍历并压入栈中;
  3. A 再从头开始遍历,每遍历一个,栈 pop 一个进行比较。
  4. 某一步不相等 => false;遍历结束后且之前未返回 => true 。

Code:

public boolean isPalindrome2(Node head){    
    if(head == null || head.next == null) //空 or 仅有一个node的List是回文串
        return true;
    Node fast = head;
    Node slow = head; 
    while(fast.next != null && fast.next.next != null ){
        slow = slow.next; //最终到mid处
        fast = fast.next.next; //最终到end处
    }
    Stack<Node> stack = new Stack<Node>();
    while(slow != null){
        stack.push(slow);
        slow = slow.next; //继续从mid遍历到end
    }
    while(!stack.isEmpty()){
        if(head.value != stack.pop().value)
            return false;
        head = head.next;
    }
    return true;
}

时间复杂度:需要遍历整个 List ,所以 T(n) = O(n) 。

空间复杂度:仅需要保存 List 的后半部分节点,相比法一少了一半,但是去除系数,S(n) = O(n) 。

法三、快慢指针升级版

法二我们已经用到了快慢指针,能否借助它们,实现彻底不用 O(N) 级别的辅助空间??

—— Yes.

首先我们能找到 List 的中点,让 List 一分为二,并让后半部分逆序;

接着前半部分从前往后,后半部分从后往前遍历,边遍历便比较。

如此,两个 node 的空间即解决了问题。

思路

  1. 快指针 A 一次走 2 步,慢指针 B 一次走 1 步;当 A 走到 List 末尾时, B 刚好到中点;
  2. 将中点的 next 指向 null ,右部分逆序;
  3. 两个 list 都从首开始遍历比较,直到结束。
  4. 得到 true / false 的结果后,记得将原结构恢复 。

细节

用 length-1 来计算:奇数个节点,中点指向null ;偶数个节点,慢指针会来到中点的前一个位置。如下图:

1167e39f9525d1173b354cf24efdb1ca.png

图2 让中点指向null

code

public boolean isPalindrome3(Node head){
    if(head == null || head.next == null)
        return true;
    Node n1 = head;
    Node n2 = head;
    while(n2.next != null && n2.next.next!=null){
        n1 = n1.next;
        n2 = n2.next.next;
    }
    n2 = n1.next; //n2 -> 右半部分第一个node
    n1.next = null; //mid.next -> null

    // convert right part  局部结构:n1 -> n2 -> n3 -> ni -> ...
    Node n3 = null;
    while(n2 != null){
        n3 = n2.next; // save n2的 next node
        n2.next = n1; // 改变n2指向
        n1 = n2; //移动n1
        n2 = n3; //移动n2
    }

    n3 = n1; //此时n2为空,n1为last node ,用n3记录下来便与恢复结构 
    n2 = head; //n2 为left part first node
    boolean res = true; //保存结果,最后才返回

    //check 回文
    while(n1 != null && n2 != null){
        if(n1.value != n2.value){
            res = false;
            break;
        }
        n1 = n1.next;
        n2 = n2.next;
    }

    //recover List
    n1 = n3.next;
    n3.next = null;
    while(n1 != null){
        n2 = n1.next;
        n1.next = n3;
        n3 = n1;
        n1 = n2;
    }

    return res;

}

时间复杂度:需要遍历整个 List ,所以 T(n) = O(n) 。

空间复杂度:仅仅用到了两个额外空间,S(n) = O(1) 。

法三的注意点:不能直接得到结果后立刻返回,要记得把原结构恢复;反转链表的操作要细心。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值