判断链表是否是镜像, 回文链表

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

输入: 1->2 输出: false

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

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

你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
https://leetcode-cn.com/problems/palindrome-linked-list/submissions/


简单粗暴的思路1, 根据此链表重新生成一个反转后的链表, 然后从头开始比较2个链表的元素是否一致.

时间上遍历生成新链表, 在遍历比较新旧链表, 总共需要遍历2次, 时间复杂度是O(n);  空间上, 重新生成了一个链表, 空间复杂度是O(n), 所以这种思路想想就可以了, 不满足要求.

还有一个小小的变种, 不需要生成一个新链表, 可以用一个栈结构保存链表, 比如1->2->1->3的链表, 存到栈中后(相当于把链表倒序),栈顶元素就是3, 然后拿栈顶在和链表中的元素依次比较. 但是也需要额外的O(n)空间.


思路二, 借助一个栈,使用快慢指针找到链表的中点位置, 每次都把慢指针的值入栈, 当快指针结束的时候, 在依次比较当前的慢指针值和栈顶元素是否一致.

举个例子, 比如1->2->2->1, 当fast指针为null时, 栈中元素为1,2, 然后此时在取出slow指针的与栈顶元素比对, 如果一致, 继续下一轮,如果不一致,返回false, 直到末尾.

当链表长度为奇数时, 1->2->3->2->1, 当fast.next为null是, 栈中元素为1,2,3, 栈顶需要先pop一次, 然后slow指针在与栈顶元素进行比对, 直到末尾.

这种思路时间复杂度是O(n), 但是空间复杂度,需要一个额外的栈空间, 只不过是O(n/2),也不满足题目要求.


思路3 , 还是使用快慢指针法找到中点, 然后把后半段的反转过来,  然后使用反转后的head 和 原始的head开始比对,直到中点位置,

比如 , 1->2->2->1 , 找到中点位置的1->2->2->1, 之后 把后半段反转, 变成 1->2->  <-2<-1 , 然后这时候从开始和末尾开始比对每一个元素, 直到中点位置.

同样看下奇数的情况, 1->2->3->2->1, 从中点3开始反转, 变成1->2-> 3 <-2<-1, 反转完成后, 从开始末尾同时比对元素, 直到中点位置.

时间复杂度, 找到中点并反转需要一次遍历, 从开始末尾开始比对, 需要遍历n/2,  总计需要O(n), 空间复杂度满足要求,O(1)即可.

先贴一个swift的初始版本, 还没有优化完成.

public class ListNode {
    public var val: Int
    public var next: ListNode?
    public init() { self.val = 0; self.next = nil; }
    public init(_ val: Int) { self.val = val; self.next = nil; }
    public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
}

func printNode(node: ListNode?) {

    var temp = node
    var str = ""

    while temp != nil {
        str += temp!.val.description + "->"
        temp = temp?.next
    }
    print(str)
}

class Solution {
    func isPalindrome(_ head: ListNode?) -> Bool {

//        printNode(node: head)

        var fast = head
        var slow = head

        // 1.找到中点位置的节点
        var pre = slow
        while fast != nil {
            pre = slow

            fast = fast?.next
            slow = slow?.next
            fast = fast?.next

        }
        // 2.翻转slow后面的链表
        while slow != nil {
            let next = slow?.next
            slow?.next = pre
            pre = slow
            slow = next
        }
        slow = pre
//        printNode(node: slow)

        var temp = head
        while (slow != nil) && (temp != nil)  {

            if slow?.val == temp?.val {
                slow = slow?.next
                temp = temp?.next

            } else {
                return false
            }


            if slow?.next?.next === slow {

                if slow?.val == temp?.val {
                    return true
                } else {
                    return false
                }

            }
        }

        return true
    }
}

 


上面这个算法基本满足条件了, 但是还是有优化空间的, 上面的思路是反转后半段链表, 改成反转前半段链表,第一遍通过快慢指针的时候, 就把前半段反转, 当到达中点位置时, slow.next和中点同时开始出发,直到结束.

 比如 : 1->2->2->1 , 找到中点位置的1<-2  2->1,  

1->2->3->2->1, 从中点3开始反转, 变成1<-2<- 3  2->1, 

class Solution {
    func isPalindrome(_ head: ListNode?) -> Bool {

        var fast = head
        var slow = head
        var pre = head

        // 1.找到中点位置的节点
        while fast != nil && fast?.next != nil {

            // fast前进2步
            fast = fast?.next?.next

            // 翻转slow经过的链表
            let next = slow?.next
            slow?.next = pre
            pre = slow
            slow = next
        }

        // 根据链表长度是奇数还是偶数判断
        // 链表是奇数, fast为最后一个节点;  链表为偶数,fast是nil
        if fast != nil {
            slow = slow?.next // 经过赋值后,slow是后续链表的起始,pre是前半链表的起始
        }

        while (slow != nil) && (pre != nil)  {

            if slow?.val == pre?.val {
                slow = slow?.next
                pre = pre?.next
            } else {
                return false
            }
        }

        return true
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值