一、回文链表
方法一:双指针
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
vector<int> vals;
while (head) {
vals.emplace_back(head->val);
head = head->next;
}
for (int i = 0, j = vals.size() - 1; i < j; i++, j--) {
if (vals[i] != vals[j])
return false;
}
return true;
}
};
思路:把链表的值存入一个vector,然后首尾双指针判断值是否相等。
复杂度分析
- 时间复杂度:O(n),其中 n 指的是链表的元素个数。
- 空间复杂度:O(n),其中 n 指的是链表的元素个数,我们使用了一个数组列表存放链表的元素值。
查漏补缺:
vals.emplace_back(head->val);
作用:将 head->val 的值直接构造到 vals 的末尾。
for (int i = 0, j = vals.size() - 1; i < j; i++, j--) { if (vals[i] != vals[j]) return false; }
作用:计算 vals 的size 。
方法二:快慢指针
快慢指针参考 详解双指针算法(一)之快慢指针
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
// 寻找中间结点(若有两个返回第二个)
ListNode* midNode(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
// 逆序
ListNode* reverse(ListNode* head) {
ListNode* pre = nullptr;
while (head) {
ListNode* newnode = head -> next;
head->next = pre;
pre = head;
head = newnode;
}
return pre;
}
bool isPalindrome(ListNode* head) {
ListNode* mid = midNode(head);
// 给后半段逆序
ListNode *head2 = reverse(mid);
while (head2) {
if (head->val != head2->val) { // 不是回文链表
return false;
}
head = head->next;
head2 = head2->next;
}
return true;
}
};
复杂度分析
- 时间复杂度:O(n),其中 n 是链表的长度(节点个数)。
- 空间复杂度:O(1)。
与这道题最类似的一道题(找终结点+反转)重排序链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverse(ListNode* head) {
ListNode* pre = nullptr;
while (head) {
ListNode* temp = head->next;
head->next = pre;
pre = head;
head = temp;
}
return pre;
}
ListNode* midNode(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
void reorderList(ListNode* head) {
ListNode* mid = midNode(head);
ListNode* head2 = reverse(mid->next);
ListNode* head1 = head;
mid->next = nullptr;
while (head2) {
ListNode* beh1 = head1->next;
ListNode* beh2 = head2->next;
head1->next = head2;
head2->next = beh1;
head1 = beh1;
head2 = beh2;
}
}
};
易错点1:
必须要让mid是前半部分的最后一个节点,mid->next是后半部分的第一个节点才行。
因为重排逻辑决定了必须要是123 45才能正确重排。
易错点2:
由于mid是前半部分的最后一个节点,就必须显示规定一个 mid->next=nullptr,这样才能保证前后两段完全断开,保证重排逻辑正确。
二、环形链表
方法一:快慢指针
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
复杂度分析
- 时间复杂度:O(N),其中 N 是链表中的节点数。
- 空间复杂度:O(1)。我们只使用了两个指针的额外空间。
类似题目:
class Solution {
public:
int calcu(int n) {
int res = 0;
while (n) {
res += (n % 10) * (n % 10);
n /= 10;
}
return res;
}
bool isHappy(int n) {
int slow = n;
int fast = calcu(n);
while (slow != fast) {
slow = calcu(slow);
fast = calcu(calcu(fast));
if (fast == 1)
return true;
}
if (fast != 1)
return false;
else
return true;
}
};
疑点解析1:
会不会陷入无休止循环?不会。
疑点解析2:
如果fast==slow!=1是否还要继续判断?不用。
因为二者相等时,说明slow已经进入了循环,不可能再出现1的情况了。
方法二:哈希表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> visited;
ListNode *x=head;
while(x){
if(visited.count(x))
return true;
visited.insert(x);
x=x->next;
}
return false;
}
};
思路:使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。
复杂度分析
- 时间复杂度:O(N),其中 N 是链表中的节点数。最坏情况下我们需要遍历每个节点一次。
- 空间复杂度:O(N),其中 N 是链表中的节点数。主要为哈希表的开销,最坏情况下我们需要将每个节点插入到哈希表中一次。