今天是休息日,所以有时间练一道题。
最近也在学习数据库,之后会更新一个图书管理系统的完整实现。
题目
234. 回文链表 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表(回文序列是向前和向后读都相同的序列)。如果是,返回 true ;否则,返回 false 。
我的思路
将链表每个结点的val值存放到一个数组中,然后对数组检测其是否"回文"。
这道题最开始想到的是:用两个指针指向链表的头和尾,然后比较链表的头尾值,然后向中间移动指针,不相等返回false,两个指针相遇的过程一直都相等,则返回true,但是题目的链表为单向的,无法直接获取一个结点的前驱结点,所以我想到了将链表各结点的值存放的数组中,利用这个办法实现比较。
创建数组,需要知道长度,所以需要先遍历一次链表,获取结点数作为数组的长度。
然后再次遍历链表,将各结点的值存入到数组中
最后"遍历"数组,判断是否回文
代码
cpp
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head->next == NULL) {
return true;
}
ListNode* p = head;
int len = 0;
while (p != NULL) {
len++;
p = p->next;
}
vector<int> arr(len);
p = head;
for (int i = 0; i < len && p != NULL; i++) {
arr[i] = p->val;
p = p->next;
}
int left = 0, right = len - 1;
while (left < right) {
if (arr[left] != arr[right]) {
return false;
}
left++;
right--;
}
return true;
}
};
复杂度
n为链表的结点数
时间复杂度:O(n)。三次遍历,第一、二次都是遍历整个链表,时间复杂度为O(n),第三次算是遍历半个链表O(n/2),所以总的时间复杂度为O(n)+O(n)+O(n/2)=O(n)。
空间复杂度:O(n)。数组申请的空间为n。
官方题解
方法一:快慢指针
要想让空间复杂度为O(1),就需要对链表自身做处理,将链表分为前后两部分,因为无法直接获取结点的前驱,所以可以将后半部分链表反转,这样就可以直接比较两部分各个结点的值,比较完成后,将后半部分链表再反转回来即可,因为使用该函数的人并不希望链表结构被改变。
但是该方法也有缺点:在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。
根据以上说明,算法流程可分为以下几步:
1.找到前半部分链表的尾结点
2.反转后半部分链表:
可以直接使用练题100天------DAY43:统计前后缀下标Ⅰ+反转链表-CSDN博客的函数
3.比较结点值,判断是否回文
4.恢复链表
5.返回结果
代码
cpp
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head == nullptr) {
return true;
}
// 找到前半部分链表的尾节点并反转后半部分链表
ListNode* firstHalfEnd = endOfFirstHalf(head);
ListNode* secondHalfStart = reverseList(firstHalfEnd->next);
// 判断是否回文
ListNode* p1 = head;
ListNode* p2 = secondHalfStart;
bool result = true;
while (result && p2 != nullptr) {
if (p1->val != p2->val) {
result = false;
}
p1 = p1->next;
p2 = p2->next;
}
// 还原链表并返回结果
firstHalfEnd->next = reverseList(secondHalfStart);
return result;
}
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr != nullptr) {
ListNode* nextTemp = curr->next;
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
ListNode* endOfFirstHalf(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast->next != nullptr && fast->next->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
复杂度
n 指的是链表的大小
时间复杂度:O(n)。
空间复杂度:O(1)。只会修改原本链表中节点的指向,而在堆栈上的堆栈帧不超过 O(1)。
方法二:递归
这种方法相较于前面两种方法,更难理解。
这里也只给出代码,感兴趣的可以去官方题解看。
代码
cpp
class Solution {
ListNode* frontPointer;
public:
bool recursivelyCheck(ListNode* currentNode) {
if (currentNode != nullptr) {
if (!recursivelyCheck(currentNode->next)) {
return false;
}
if (currentNode->val != frontPointer->val) {
return false;
}
frontPointer = frontPointer->next;
}
return true;
}
bool isPalindrome(ListNode* head) {
frontPointer = head;
return recursivelyCheck(head);
}
};