题目概述
链接:点我做题
题解
一、数组接收链表值的前后指针法
单链表不支持随机访问,也不像双向循环链表一样支持 O ( 1 ) O(1) O(1)的回溯操作,所以我们可以先用一个vector容器遍历一遍链表,把链表的值都放到数组中,然后用判断数组是否为回文数组的前后指针法,直接ko。
class Solution {
public:
bool isPalindrome(ListNode* head)
{
if (head->next == nullptr)
{
return true;
}
vector<int> vec;
ListNode* cur = head;
while (cur)
{
vec.push_back(cur->val);
cur = cur->next;
}
int left = 0;
int right = vec.size() - 1;
while (left < right)
{
if (vec[left] != vec[right])
{
return false;
}
left++;
right--;
}
return true;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
二、利用栈“翻转”链表顺序
好吧,多少有点标题党,其实这个方法并没有真正的去逆置链表,而是利用栈这种数据结构"先进后出"的特点,先遍历一遍链表,让每个结点依次入栈,然后再遍历一遍链表,此时链表顶的节点就是最后一个结点,如果这个节点的值不等于当前遍历的链表的结点位置的值,就返回false,否则就pop掉这个节点,遍历指针进入链表的下一个位置,如果能走出循环,则返回true。
class Solution {
public:
bool isPalindrome(ListNode* head)
{
if (head->next == nullptr)
{
return true;
}
stack<int> st;
ListNode* cur = head;
while (cur)
{
st.push(cur->val);
cur = cur->next;
}
cur = head;
while (cur)
{
int val = st.top();
st.pop();
if (val != cur->val)
{
return false;
}
cur = cur->next;
}
return true;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
三、利用递归判断回文链表
不妨这样思考,既然栈可以实现判断回文链表,那么用递归也一定可以实现,因为递归本身就是利用函数栈帧的建立和销毁来实现的,它本身就是一种栈算法,仿照栈的思路,我们设置一个全局变量
f
r
o
n
t
p
o
i
n
t
e
r
frontpointer
frontpointer从head开始走,一个局部变量
h
e
a
d
head
head表示当前栈帧中的结点位置,先通过建立栈让
h
e
a
d
head
head走到链表尾巴,当
h
e
a
d
head
head走到
n
u
l
l
p
t
r
nullptr
nullptr后,返回true到上一层,这时就返回了到了倒数第一个结点,此时
f
r
o
n
t
p
o
i
n
t
e
r
frontpointer
frontpointer指向正数第一个结点,然后检查
f
r
o
n
t
p
o
i
n
t
e
r
−
>
v
a
l
frontpointer->val
frontpointer−>val和
h
e
a
d
−
>
v
a
l
head->val
head−>val,如果不相等就返回false,如果相等就让
f
r
o
n
t
p
o
i
n
t
e
r
=
f
r
o
n
t
p
o
i
n
t
e
r
−
>
n
e
x
t
;
frontpointer = frontpointer->next;
frontpointer=frontpointer−>next;,然后向上一层返回true,思路大概如此,我们照着代码讲解。
代码:
class Solution {
public:
ListNode* frontpointer;
//此指针作为全局变量 来从头遍历链表
bool isPalindrome(ListNode* head)
{
frontpointer = head;
return CheckList(head);
}
bool CheckList(ListNode* head)
{
//如果head不为空 就往前走
if (head)
{
//利用这里同时做到了让head走到链表尾
//和如果上一层返回false
//这一层也返回false 表示不是回文链表
if (!CheckList(head->next))
{
return false;
}
//检查head->val和frontpointer->val
//如果不相等 在这里就向上一层返回false
if (head->val != frontpointer->val)
{
return false;
}
//没在上面返回的话 说明这一层通过测验
//应该让全局变量frontpointer往前走一步
frontpointer = frontpointer->next;
}
//同时完成了往前走一步后向上一层返回true
//和head走到空了 向上一层返回true
return true;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n),建立了n数量级的栈帧
四、快慢指针找中点+逆置链表判回文
终于来到了一个空间复杂度是
O
(
1
)
O(1)
O(1)的算法了,思路很简单,首先利用快慢指针法,找到链表的中点,然后去逆转链表中点mid后的部分,即逆转
m
i
d
−
>
n
e
x
t
mid->next
mid−>next,并且返回逆转后的头结点用check和cure接收,然后cur从链表头开始走,check也开始走,如果
c
h
e
c
k
−
>
v
a
l
!
=
c
u
r
−
>
v
a
l
check->val != cur->val
check−>val!=cur−>val,说明不是回文链表,就终止遍历,如果相等则继续,直到check走到nullptr为止。然后修补原链表,就是把cure链表逆置,然后用
m
i
d
−
>
n
e
x
t
mid->next
mid−>next去接收返回值以修补链表.
逆置链表据我所知有两种方法:一种是原地逆置法,需要需要逆置的结点
c
u
r
cur
cur和它的前置结点
p
r
e
v
prev
prev,对链表头,其前置结点就设为
n
u
l
l
p
t
r
nullptr
nullptr,然后先以局部变量next存储起来
c
u
r
−
>
n
e
x
t
cur->next
cur−>next,然后修改
c
u
r
−
>
n
e
x
t
cur->next
cur−>next为prev,然后
p
r
e
v
=
c
u
r
;
c
u
r
=
n
e
x
t
prev = cur; cur = next
prev=cur;cur=next走到下一结点进行逆置;另一种方法是头插法,我们创建一个新链表,其头结点为
n
e
w
h
e
a
d
newhead
newhead,初始时设置为
n
u
l
l
p
t
r
nullptr
nullptr,然后不断把新节点头插入当前链表,就实现了逆置链表。
代码:
class Solution {
public:
bool isPalindrome(ListNode* head)
{
//边界条件
if (head->next == nullptr)
{
return true;
}
//快慢指针找中点
ListNode* fast = head;
ListNode* slow = head;
while (fast != nullptr
&& fast->next != nullptr
&& fast->next->next != nullptr)
{
fast = fast->next->next;
slow = slow->next;
}
ListNode* mid = slow;
ListNode* check = reverseList(mid->next);
ListNode* cure = check;
bool ret = true;
ListNode* cur = head;
while (check)
{
if (check->val != cur->val)
{
ret = false;
break;
}
check = check->next;
cur = cur->next;
}
mid->next = reverseList(cure);
return ret;
}
ListNode* reverseList(ListNode* head)
{
//遍历一遍链表 把结点头插进新链表就是逆置了
ListNode* newnode = nullptr;
ListNode* cur = head;
while (cur)
{
ListNode* next = cur->next;
cur->next = newnode;
newnode = cur;
cur = next;
}
return newnode;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)