请判断一个链表是否为回文链表。
输入: 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
}
}