leetcode之链表总结
1. 反转链表(简单)
https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = nullptr;
ListNode* curr = head;
while(curr!=NULL){
ListNode* temp = curr->next;
curr->next = pre;
pre = curr;
curr = temp;
}
return pre;
}
};
做完这个可以做反转链表2
2. 回文链表(简单)
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
第一种方法
https://leetcode-cn.com/problems/palindrome-linked-list/
比较骚的操作:
第一步:复制链表到数组中
第二步:使用双指针法判断是否为回文
class Solution {
public:
bool isPalindrome(ListNode* head) {
ListNode* dummy = head;
vector<int> res;
while(dummy!=NULL){
res.push_back(dummy->val);
dummy = dummy->next;
}
int i=0;
int j=res.size()-1;
while(i<=j){
if(res[i]==res[j]){
i++;
j--;
}
else{
return false;
}
}
return true;
}
};
时间复杂度o(n),空间复杂度o(n)
第二种做法:快慢指针
整个流程分为五个部分:
1.找到前半部分链表的尾节点
2.反转后半部分链表
3.判断是否回文
4.恢复链表(一般情况下希望链表执行完之后不对原始链表进行改变,不要这一步,对结果也不会产生影响)
5.返回结果
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(head==NULL) return true;
ListNode *firstEnd = findMid(head);
ListNode *secondList = reverseList(firstEnd->next);
ListNode *first = head, *second = secondList;
bool result = true;
while(result && second!=NULL){
if(first->val != second->val){
result = false;
}
first = first->next;
second = second->next;
}
firstEnd->next = reverseList(secondList);
return result;
}
ListNode *reverseList(ListNode* head){
ListNode *pre = nullptr,*curr=head;
while(curr!=NULL){
ListNode *temp = curr->next;
curr->next = pre;
pre = curr;
curr = temp;
}
return pre;
}
ListNode *findMid(ListNode* head){
ListNode *slow = head,*fast = head;
while(fast->next!=NULL&&fast->next->next!=NULL){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
时间复杂度 o(n),空间复杂度 o(1)
注:这两种做法都有牢记,在不对空间复杂度做要求时,推荐第一种做法,毕竟简单。但第二种做法涉及到链表一些常见的解题技巧,快慢指针,反转链表等,也要记住。
3.移除重复节点(简单)
https://leetcode-cn.com/problems/remove-duplicate-node-lcci/
编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。
示例1:
输入:[1, 2, 3, 3, 2, 1]
输出:[1, 2, 3]
示例2:
输入:[1, 1, 1, 1, 2]
输出:[1, 2]
1.哈希表 hash_set
与去重有关的问题首选哈希表(集合)
class Solution {
public:
ListNode* removeDuplicateNodes(ListNode* head) {
if(head==NULL||head->next==NULL) return head;
unordered_set<int> occurred = {head->val};
ListNode *pos = head;
while(pos->next!=NULL){
ListNode *cur = pos->next;
if(!occurred.count(cur->val)){
occurred.insert(cur->val);
pos = pos->next;
}else{
pos->next = pos->next->next;
}
}
return head;
}
};
时间复杂度 o(n) 空间复杂度 o(n)
- 双重循环
对每一个节点保证后面的节点与他没有重复的。
class Solution {
public:
ListNode* removeDuplicateNodes(ListNode* head) {
if(head==NULL||head->next==NULL) return head;
ListNode *pos = head;
while(pos!=NULL){
ListNode *cur = pos;
while(cur->next!=NULL){
if(cur->next->val==pos->val){
cur->next = cur->next->next;
}else{
cur = cur->next;
}
}
pos = pos->next;
}
return head;
}
};
时间复杂度 o(n^2) 空间复杂度 o(1)
4.环形链表(简单)
https://leetcode-cn.com/problems/linked-list-cycle/
快慢指针的典型应用
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
进阶:你能用 O(1)(即,常量)内存解决此问题吗?
- 如果存在环,那么快慢指针一定会在某一刻相遇
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL||head->next==NULL) return false;
ListNode *slow = head->next,*fast=head->next->next;
while(slow!=fast){
if(fast==NULL||fast->next==NULL) return false;
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
时间复杂度 o(n) 空间复杂度 o(1)
2.哈希表,如果存在环,节点就会被重复添加。
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> seen;
while(head!=NULL){
if(seen.count(head)){
return true;
}else{
seen.insert(head);
head = head->next;
}
}
return false;
}
};
时间复杂度 o(n), 空间复杂度 o(n)
5.环形链表2(中等)
https://leetcode-cn.com/problems/linked-list-cycle-ii/
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
进阶:
你是否可以使用 O(1) 空间解决此题?
1.延续上一题哈希表的思想
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode*> seen;
while(head!=NULL){
if(seen.count(head)){
return head;
}else{
seen.insert(head);
head = head->next;
}
}
return NULL;
}
};
时间复杂度 o(n) 空间复杂度 o(n)
2.快慢指针
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow=head,*fast=head;
while(fast!=NULL&& fast->next!=NULL){
slow = slow->next;
fast = fast->next->next;
if(slow == fast){
ListNode *ptr = head;
while(ptr!=slow){
ptr = ptr->next;
slow = slow->next;
}
return slow;
}
}
return NULL;
}
};
涉及到数学推理,不建议
时间复杂度 o(n),空间复杂度 o(1)
6.重排链表(中等)
给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
给定链表 1->2->3->4, 重新排列为 1->4->2->3.
示例 2:
给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
1.直接双重循环进行插入
class Solution {
public:
void reorderList(ListNode* head) {
if(head==NULL||head->next==NULL) return;
ListNode *pre = head;
while(pre!=NULL&&pre->next!=NULL&&pre->next->next!=NULL){
ListNode *cur = pre;
while(cur->next->next!=NULL){
cur = cur->next;
}
cur->next->next = pre->next;
pre->next = cur->next;
cur->next=NULL;
//if(pre->next==NULL||pre->next->next==NULL) return;
pre = pre->next->next;
}
}
};
时间复杂度 o(n^2) 空间复杂度 o(1)
2.线性表
因为链表不支持下标访问,所以我们无法随机访问链表中任意位置的元素。
因此比较容易想到的一个方法是,我们利用线性表存储该链表,然后利用线性表可以下标访问的特点,直接按顺序访问指定元素,重建该链表即可。
对于链表中涉及到元素交换的都可以考虑使用线性表(数组)进行处理
class Solution {
public:
void reorderList(ListNode* head) {
if(head==NULL||head->next==NULL) return;
vector<ListNode*> res;
ListNode *cur = head;
while(cur!=NULL){
res.push_back(cur);
cur = cur->next;
}
int i=0,j = res.size()-1;
while(i<j){
res[i]->next = res[j];
i++;
if(i==j) break;
res[j]->next = res[i];
j--;
}
res[i]->next = NULL;
}
};
注意:循环跳出的条件,边界值
时间复杂度 o(n) ,空间复杂度 o(n)
3. 寻找链表中点 + 链表逆序 + 合并链表
链表常见解题方法的综合应用
class Solution {
public:
void reorderList(ListNode* head) {
if(head==NULL||head->next==NULL) return;
ListNode *firstEnd = findMid(head);
ListNode *secondList = reverseList(firstEnd->next);
firstEnd->next = NULL;
ListNode *first=head,*second = secondList;
while(second!=NULL){
ListNode *temp = second->next;
second->next = first->next;
first->next = second;
first = first->next->next;
second = temp;
}
//first->next = NULL;
}
ListNode *reverseList(ListNode* head){
ListNode *pre=NULL,*cur = head;
while(cur!=NULL){
ListNode *temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
ListNode *findMid(ListNode* head){
ListNode *slow = head,*fast = head;
while(fast->next!=NULL && fast->next->next!=NULL){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
时间复杂度 o(n) 空间复杂度 o(1)
7.有序链表转换二叉搜索树
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
分治
class Solution {
public:
TreeNode* sortedListToBST(ListNode* head) {
return buildTree(head,nullptr);
}
ListNode *findMid(ListNode* left,ListNode *right){
ListNode *slow = left,*fast = left;
while(fast!=right && fast->next!=right){
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
TreeNode *buildTree(ListNode *left,ListNode *right){
if(left==right) return nullptr;
ListNode *mid = findMid(left,right);
TreeNode* root = new TreeNode(mid->val);
root->left = buildTree(left,mid);
root->right = buildTree(mid->next,right);
return root;
}
};
时间复杂度 o(nlogn) 空间复杂度 o(logn)
总结
反转链表 + 快慢指针+找中间节点
考虑使用线性表(vector)/数组
与去重(不重复)有关的用哈希表 unordered_set m; unordered_set<ListNode*> m;