leetcode刷题链表
反转链表Ⅱ
-
题目描述将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。例如:
给出的链表为9->7->2->5->4->3->6->NULL , m=3,n=6,
返回9->7->3->4->5->2->6 ->NULL 要求:时间复杂度 O(n)O(n) ,空间复杂度 O(n)O(n)
进阶:时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)
-
解题方法:穿针引线法,在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。
-
使用三个指针变量 pre、curr、next 来记录反转的过程中需要的变量,它们的意义如下:
curr:指向待反转区域的第一个节点 left;
next:永远指向 curr 的下一个节点,循环过程中,curr 变化以后 next 会变化;
pre:永远指向待反转区域的第一个节点 left 的前一个节点,在循环过程中不变。class Solution { public: ListNode *reverseBetween(ListNode *head, int left, int right) { ListNode *dummyNode = new ListNode(-1); dummyNode->next = head; ListNode *pre = dummyNode; for (int i = 0; i < left - 1; i++) { pre = pre->next; } ListNode *cur = pre->next; ListNode *next; for (int i = 0; i < right - left; i++) { next = cur->next; cur->next = next->next; next->next = pre->next; pre->next = next; } return dummyNode->next; } };
复杂链表的复制
-
题目描述:请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
-
解题方法一:回溯+哈希表
class Solution { public: unordered_map<Node*, Node*> cachedNode; Node* copyRandomList(Node* head) { if (head == nullptr) { return nullptr; } if (!cachedNode.count(head)) { Node* newHead = new Node(head->val); cachedNode[head] = newHead; headNew->next = copyRandomList(head->next); headNew->random = copyRandomList(head->random); } return cachedNode[head]; } };
-
解题方法二:迭代 + 节点拆分,复制每一个节点并插入节点之后,每个复制节点的random 指针在前一节点random 指针的下一个指针。
class Solution { public: Node* copyRandomList(Node* head) { if (head == nullptr) { return nullptr; } for (Node* node = head; node != nullptr; node = node->next->next) { Node* nodeNew = new Node(node->val); nodeNew->next = node->next; node->next = nodeNew; } for (Node* node = head; node != nullptr; node = node->next->next) { Node* nodeNew = node->next; nodeNew->random = (node->random != nullptr) ? node->random->next : nullptr; } Node* headNew = head->next; for (Node* node = head; node != nullptr; node = node->next) { Node* nodeNew = node->next; node->next = node->next->next; nodeNew->next = (nodeNew->next != nullptr) ? nodeNew->next->next : nullptr; } return headNew; } };
链表K个一组进行翻转
-
题目描述:对于给定链表1->2->3->4->5,k=2,反转后返回2->1->4->3->5
-
解题方法:把链表节点按照 k 个一组分组,所以可以使用一个指针 head 依次指向每组的头节点。这个指针每次向前移动 k 步,直至链表结尾。对于每个分组,我们先判断它的长度是否大于等于 k。若是,我们就翻转这部分链表,否则不需要翻转。
class Solution { public: // 翻转一个子链表,并且返回新的头与尾 pair<ListNode*, ListNode*> reverseList(ListNode* head, ListNode* tail) { ListNode* prev = tail->next; ListNode* cur = head; while (prev != tail) { ListNode* nex = cur->next; cur->next = prev; prev = cur; cur= nex; } return {tail, head}; } ListNode* reverseKGroup(ListNode* head, int k) { ListNode* hair = new ListNode(0); hair->next = head; ListNode* pre = hair; while (head) { ListNode* tail = pre; // 查看剩余部分长度是否大于等于 k for (int i = 0; i < k; ++i) { tail = tail->next; if (!tail) { return hair->next; } } ListNode* nex = tail->next; auto res = reverseList(head, tail); head = res.first; tail = res.second; // 把子链表重新接回原链表 pre->next = head; tail->next = nex; pre = tail; head = tail->next; } return hair->next; } };
合并k个排序链表
-
题目描述:合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。要求:时间复杂度 O(nlogk)
-
解题方法一:分治法
class Solution { public: ListNode* mergeTwoLists(ListNode* pHead1, ListNode* pHead2) { if(pHead1==NULL) return pHead2; if(pHead2==NULL) return pHead1; ListNode*dummy= new ListNode(-1); ListNode*temp=dummy; while(pHead1 && pHead2){ if(pHead1->val>pHead2->val){ temp->next=pHead2; pHead2=pHead2->next; }else{ temp->next=pHead1; pHead1=pHead1->next; } temp=temp->next; } if(pHead1){ temp->next=pHead1; } if(pHead2){ temp->next=pHead2; } return dummy->next; } ListNode* merge(vector <ListNode*> &lists, int l, int r) { if (l == r) return lists[l]; if (l > r) return nullptr; int mid = (l + r) >> 1; return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r)); } ListNode *mergeKLists(vector<ListNode *> &lists) { int n=lists.size(); return merge(lists,0,n-1); } };
-
解题方法二:使用优先队列去存储所有链表。按照链表头结点值,进行从小到大的排序,最小的头结点的链表在堆顶。
- 每次将堆顶的链表取出
- 将头结点从取出的链表上去除,并插在所需目标链表的尾部。
- 将取出的链表放回堆中。若链表为null,则不放回。
重复1,2,3过程,直到堆为空,循环终止。
class Solution {
public:
struct Aux{
int val;
ListNode * ptr;
bool operator <(const Aux& aux) const{
return val>aux.val;
}
};
priority_queue<Aux> myqueue;
ListNode *mergeKLists(vector<ListNode *> &lists) {
int n=lists.size();
if(n==0) return NULL;
if(n==1) return lists[0];
for(auto node:lists){
if(node){
Aux a={node->val,node};
myqueue.push(a);
}
}
ListNode*dummy=new ListNode(-1);
ListNode * pre=dummy;
while(!myqueue.empty()){
Aux aux=myqueue.top();
myqueue.pop();
pre->next=aux.ptr;
pre=pre->next;
ListNode * nxt=aux.ptr->next;
if(nxt==NULL) continue;
myqueue.push({nxt->val,nxt});
}
return dummy->next;
}
};
删除链表的第N个节点
-
题目描述:给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。 -
解题方法一:双指针
-
解题方法二:栈先进后出,在遍历链表的同时将所有节点依次入栈。弹出栈的第 N个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode * dummy=new ListNode(-1);
dummy->next=head;
ListNode * cur=dummy;
stack<ListNode*> stk;
while(cur){
stk.push(cur);
cur=cur->next;
}
for(int i=0;i<n;++i){
stk.pop();
}
ListNode * pre=stk.top();
pre->next=pre->next->next;
return dummy->next;
}
};
两个链表的第一个公共节点
-
题目描述:输入两个无环的单向链表L1,L2,找出它们的第一个公共结点,如果没有公共节点则返回空。
-
解题方法:双指针。两个指针分别指向L1,L2,保证两指针走过的距离相等,当指针走到链表NULL节点(末尾节点的后继),令其等于另一个链表的头节点,直到两个指针相遇为止,如果有公共节点,两个指针将在此节点处相遇,如果没有则会在NULL相遇。
class Solution { public: ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) { ListNode * p1=pHead1; ListNode * p2=pHead2; while(p1 != p2){ p1=p1==NULL?pHead2:p1->next; p2=p2==NULL?pHead1:p2->next; } return p1; } };
链表相加Ⅱ
-
题目描述:假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数,头节点存放整数的高位。给定两个这种链表,请生成代表两个整数相加值的结果链表。例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。
-
解题方法:整数相加由低位到高位,两个链表从末尾节点开始相加,方向与指针方向相反,考虑栈先进后出的特点,利用两个辅助栈,相加后用头插法构造新链表。
class Solution { public: ListNode* addInList(ListNode* head1, ListNode* head2) { // write code here stack<int> stk1; stack<int> stk2; while(head1){ stk1.push(head1->val); head1=head1->next; } while(head2){ stk2.push(head2->val); head2=head2->next; } int carry=0; ListNode * temp=NULL; while(!stk1.empty()||!stk2.empty()){ int s1,s2; if(!stk1.empty()){ s1=stk1.top(); stk1.pop(); }else{ s1=0; } if(!stk2.empty()){ s2=stk2.top(); stk2.pop(); }else{ s2=0; } int sum=s1+s2+carry; ListNode* headNew=new ListNode(sum%10); carry=sum/10; headNew->next=temp; temp=head; } if(carry!=0){ ListNode* headNew=new ListNode(carry); headNew->next=temp; temp=head; } return temp; } };
单链表排序
-
题目描述:给定一个节点数为n的无序单链表,对其按升序排序。数据范围:0 < <n≤100000;要求:时间复杂度*** O(nlogn)***
-
解题方法一:自顶向下归并排序。
- 找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。
- 对两个子链表分别排序。
- 将两个排序后的子链表合并,得到完整的排序后的链表。
class Solution { public: ListNode* merge(ListNode* head1, ListNode* head2) { ListNode* dummyHead = new ListNode(0); ListNode* temp = dummyHead; while (head1 != NULL && head2 != NULL) { if (head1->val <= head2->val) { temp->next = head1; head1 = head1->next; } else { temp->next = head2; head2 = head2->next; } temp = temp->next; } if (head1 != NULL) { temp->next = head1; } else if (head2 != NULL) { temp->next = head2; } return dummyHead->next; } ListNode * sortlist(ListNode*head,ListNode*tail){ if(head==NULL){ return head; } if(head->next==tail){ //当链表中只有两个元素时,断开指针并返回第一个元素 head->next=NULL; return head; } ListNode*fast=head; ListNode*slow=head; while(fast!=tail){ fast=fast->next; slow=slow->next; if(fast!=tail) fast=fast->next; } ListNode * mid=slow; return merge(sortlist(head,mid), sortlist(mid,tail));//左右两半递归均包含mid,当仅剩两个节点时丢弃第二个 } ListNode* sortInList(ListNode* head) { return sortlist(head, NULL); } };
-
解题方法二:自底向上归并排序
首先求得链表的长度 length,然后将链表拆分成子链表进行合并。具体做法如下。
-
用 subLength 表示每次需要排序的子链表的长度,初始时subLength=1。
-
每次将链表拆分成若干个长度为 subLength 的子链表(最后一个子链表的长度可以小于subLength),按照每两个子链表一组进行合并,合并后即可得到若干个长度为subLength×2 的有序子链表(最后一个子链表的长度可以小于 subLength×2)。合并两个子链表仍然使用合并两个有序链表的做法。
-
将subLength 的值加倍,重复第 2 步,对更长的有序子链表进行合并操作,直到有序子链表的长度大于或等于 length,整个链表排序完毕。
class Solution { public: ListNode* merge(ListNode* head1, ListNode* head2) { ListNode* dummyHead = new ListNode(0); ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2; while (temp1 != NULL && temp2 != NULL) { if (temp1->val <= temp2->val) { temp->next = temp1; temp1 = temp1->next; } else { temp->next = temp2; temp2 = temp2->next; } temp = temp->next; } if (temp1 != NULL) { temp->next = temp1; } else if (temp2 != NULL) { temp->next = temp2; } return dummyHead->next; } ListNode* sortInList(ListNode* head) { if(head == NULL || head->next == NULL) return head; int nodeNum=0; ListNode * temp1=head; while(temp1){ nodeNum++; temp1=temp1->next; } ListNode*dummy=new ListNode(0); dummy->next=head; for(int sublength=1;sublength<nodeNum;sublength<<=1){ ListNode*pre=dummy; ListNode*cur=dummy->next; while(cur!=NULL){ ListNode*head1=cur; for(int i=1;i<sublength&&cur->next != NULL;++i){ cur=cur->next; } ListNode*head2=cur->next; cur->next=NULL; cur=head2; for(int i=1;i<sublength&&cur!=NULL&&cur->next != NULL;++i){ cur=cur->next; } if(cur!=NULL){ nxt=cur->next; cur->next=NULL; } ListNode* merged=merge(head1,head2); pre->next=merged; while(pre->next!=NULL){ pre=pre->next; } cur=nxt; } } return dummy->NULL; } };
-
判断一个链表是否为回文结构
-
题目描述:给定一个链表,请判断该链表是否为回文结构。回文是指该字符串正序逆序完全一致。
-
解题方法一:将链表转化为数组
-
解题方法二:快慢指针。初始化fast与slow指向头节点,fast移动速度是slow的两倍,fast到链表末尾时,slow到达中心位置;翻转后半部分的链表,与前半部分进行比较。
class Solution { public: ListNode* reservelist(ListNode*head){ ListNode* pre=NULL; while(head){ ListNode * nxt=head->next; head->next=pre; pre=head; head=nxt; } return pre; } bool isPail(ListNode* head) { ListNode * fast=head,*slow=head; while(fast&& fast->next ){ fast=fast->next->next; slow=slow->next; } if(fast!=NULL){ //当fast不为NULL时,链表节点个数为奇数,此时slow为中心节点 slow=slow->next; } fast=head; //fast重新指向头节点 slow=reservelist(slow); //翻转后半链表 while(slow){ if(fast->val!=slow->val) return false; fast=fast->next; slow=slow->next; } return true; } };
链表的奇偶重排
-
题目描述:给定一个单链表,请设定一个函数,将链表的奇数位节点和偶数位节点分别放在一起,重排后输出。
注意是节点的编号而非节点的数值。
-
解题方法:双指针,第一个指针为奇数位,第二个指针位偶数位,奇数位在前,偶数位在后
class Solution { public: ListNode* oddEvenList(ListNode* head) { //如果链表为空,不用重排 if(head == NULL) return head; //even开头指向第二个节点,可能为空 ListNode* even = head->next; //odd开头指向第一个节点 ListNode* odd = head; //指向even开头 ListNode* evenhead = even; while(even != NULL && even->next != NULL){ //odd连接even的后一个,即奇数位 odd->next = even->next; //odd进入后一个奇数位 odd = odd->next; //even连接后一个奇数的后一位,即偶数位 even->next = odd->next; //even进入后一个偶数位 even = even->next; } //even整体接在odd后面 odd->next = evenhead; return head; } };
删除链表中的重复元素Ⅰ
-
题目描述:删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次例如:给出的链表为1->1->2,返回1-> 2。
-
解题方法:遇到连续相同的元素,保留第一个
-
class Solution { public: ListNode* deleteDuplicates(ListNode* head) { // write code here if(!head) return NULL; ListNode * cur=head; while(cur&&cur->next){ ListNode *nxt=cur->next; if(cur->val==nxt->val){ //相邻元素相同,跳过下一位,之后的元素继续与上一位比较 cur->next=nxt->next; }else{ cur=cur->next; //相邻元素不同,正常遍历 } } return head; } };
删除链表中重复元素Ⅱ
- 题目描述:给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。给出的链表为1->1->1->2->3,返回2->3。
- 解题方法:这是一个升序链表,重复的节点都连在一起,我们就可以很轻易地比较到重复的节点,然后将所有的连续相同的节点都跳过,连接不相同的第一个节点。注意添加一个虚拟的头节点,以便于删除第一个节点。
- 链表前加虚拟的头节点。
- 遍历链表,从虚拟节点的后继节点开始每次比较相邻两个节点,如果遇到了两个相邻节点相同,则新开内循环将这一段所有的相同都遍历过去。
- 在2中这一连串相同的节点前的节点直接连上后续第一个不相同值的节点。
- 返回时去掉添加的表头。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(!head) return NULL;
ListNode * dummy=new ListNode(0);
dummy->next=head;
ListNode * cur=dummy;
while(cur->next && cur->next->next){
if(cur->next->val==cur->next->next->val){
int num=cur->next->val;
while(cur->next!=NULL&&cur->next->val==num){
cur->next=cur->next->next;
}
}else{
cur=cur->next;
}
}
return dummy->next;
}
};
返回时去掉添加的表头。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(!head) return NULL;
ListNode * dummy=new ListNode(0);
dummy->next=head;
ListNode * cur=dummy;
while(cur->next && cur->next->next){
if(cur->next->val==cur->next->next->val){
int num=cur->next->val;
while(cur->next!=NULL&&cur->next->val==num){
cur->next=cur->next->next;
}
}else{
cur=cur->next;
}
}
return dummy->next;
}
};