牛客101:链表
目录
一、反转链表
二、链表内指定区间反转
三、链表中的节点每k个一组翻转
四、合并两个排序的链表
五、合并k个已排序的链表
六、判断链表中是否有环
七、链表中环的入口结点
八、链表中倒数最后k个结点
九、删除链表的倒数第n个节点
十、两个链表的第一个公共结点
十一、链表相加 II
十二、单链表的排序
十三、判断一个链表是否为回文结构
十四、链表的奇偶重排
十五、删除有序链表中重复的元素
十六、删除有序链表中重复的元素II
一、反转链表
反转链表_牛客题霸_牛客网
法一:双指针定位(必须掌握)
创建prev和cur的Node,prev先设置为nullptr,cur设置为head,先存一个t=cur->next(不然往后更新就找不到了),然后修改cur->next为prev,然后prev更新为cur,cur更新为存的t。最后返回prev,画个图很明了。
ListNode* ReverseList(ListNode* head) {ListNode* prev=nullptr,*cur=head;while(cur!=nullptr){ListNode*tmp=cur->next;cur->next=prev;prev=cur;cur=tmp;}return prev;}
法二:递归
如果head是空直接返回
递归处理,递归退出条件是找到最后一个节点
每层处理的子问题就是让我此刻的head->next的箭头反转,并且head->next箭头也反转,此时因为还未到达上一层递归调用,先指向nullptr,这一步也是为了修改原始头结点的下一个为nullptr
ListNode* ReverseList(ListNode* head) {if(!head||!head->next)return head;//找到最后一个节点ListNode*newnode=ReverseList(head->next);//处理每个子问题head->next->next=head;head->next=nullptr;//最后的这个节点作为头返回return newnode;}
二、链表内指定区间反转
链表内指定区间反转_牛客题霸_牛客网
单链表逆转往往涉及四个节点问题,可以先遍历,找到要逆转的起点,然后重复以下操作n-m次即可,每次更新后,cur恰好变成了我们想要的逆转那个节点,重复操作
1.t=cur->next; 存cur->next,此时t是我们要逆转它的方向的节点
2.存好cur->next后修改cur->next=t->next;让cur连接到下次的“t”
3.然后t->next=pre->next; prev->next存的是上次的“t”将此刻的t->next=上次的"t"(prev->next)
4.prev->next=t; 更新prev->next(存的是每次逆转的那个节点)
既然我们需要prev这个节点,那么我们当我们的原始链表头结点需要操作的时候,就需要一个虚拟头结点
ListNode* reverseBetween(ListNode* head, int m, int n) {ListNode* newhead=new ListNode(0);newhead->next=head;ListNode*prev=newhead,*cur=head;for(int i=1;i<m;++i){prev=cur;cur=cur->next;}//循环操作n-m次,可以举个特例n=m的时候就不需要逆转for(int i=m;i<n;++i){ListNode*t=cur->next;cur->next=t->next;t->next=prev->next;prev->next=t;}return newhead->next;}
三、链表中的节点每k个一组翻转
链表中的节点每k个一组翻转_牛客题霸_牛客网
可以提前设定一个Node(tail)从head开始往后走k步,确定逆转的终点,如果还没到k,tail就到末尾了,那么直接返回head就行,表示不用反转。
创建prev和cur的Node,prev先设置为nullptr,cur设置为head,存一个t=cur->next,然后修改cur->next为prev,然后prev更新为cur,cur更新为存的t。当cur=tail终止这个循环
最后我们让head->next指向以tail为头节点的递归结果即可。注意最后返回prev,画画图就明了了。
ListNode* reverseKGroup(ListNode* head, int k) {ListNode*tail=head;//找到前k的节点后的一个节点//该节点既可以用在这层当作结束标志//也可以作为下一层的开始节点for(int i=0;i<k;++i){//先判断!if(tail==nullptr)return head;tail=tail->next;}ListNode*prev=nullptr,*cur=head;while(cur!=tail){//存一下下个节点ListNode*tmp=cur->next;//逆转cur->next=prev;//更新prev=cur;cur=tmp;}head->next=reverseKGroup(tail,k);return prev;}
四、合并两个排序的链表
合并两个排序的链表_牛客题霸_牛客网
很简单,因为单调性,所以双指针
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {//虚拟头结点ListNode*newhead=new ListNode(-1);ListNode*tail=newhead;ListNode*cur1=pHead1,*cur2=pHead2;while(cur1&&cur2){if(cur1->val<cur2->val){ tail->next=cur1;cur1=cur1->next;}else {tail->next=cur2;cur2=cur2->next;}tail=tail->next;}if(cur1)tail->next=cur1;else tail->next=cur2;return newhead->next;}
五、合并k个已排序的链表
合并k个已排序的链表_牛客题霸_牛客网
先思考暴力思路是怎样的:因为之前我们写过一道题叫合并两个升序链表。那么我们可以先合并前两个,然后再让这个合并的链表跟第三个合并,然后再依次这样合并下去就能完成。这其实很有一股递归的味道。
法一:优先级队列,使得操作很简单
class Solution {
public:struct cmp{bool operator()(ListNode* l1,ListNode* l2){return l1->val > l2->val;}};ListNode* mergeKLists(vector<ListNode*>& lists) {//创建小根堆(greater)priority_queue<ListNode*,vector<ListNode*>,cmp> heap;//放头节点for(auto l:lists)if(l)heap.push(l);//可能会有空数组ListNode* ret=new ListNode(0),*tail=ret;//比较各个数组中哪个更小,添加到链表while(!heap.empty()){ListNode*t=heap.top();heap.pop();tail->next=t;tail=tail->next;//让插入链表的那个节点下一个节点继续入小根堆if(t->next)heap.push(t->next);}tail=ret->next;delete ret;return tail;}
};
法二:分治
其实很容易想到分治,将数组无限缩小,缩小到只有两个,处理这两个(根据快排归并排序的思路),但是怎么找到每段的头的位置,我去突然发现这里是数组,可以直接找了。
class Solution {
public:ListNode* mergeKLists(vector<ListNode*>& lists) {return mergeSort(lists,0,lists.size()-1);}ListNode* mergeSort(vector<ListNode*>&lists,int left,int right){//边界处理if(left>right)return nullptr;if(left==right)return lists[left];//分治int mid=(left+right)>>1;ListNode* l=mergeSort(lists,left,mid);ListNode* r=mergeSort(lists,mid+1,right);//合并return mergeTwoList(l,r);}ListNode* mergeTwoList(ListNode*list1,ListNode*List2){if(list1==nullptr)return List2;if(List2==nullptr)return list1;ListNode*cur1=list1,*cur2=List2;ListNode* head=new ListNode(0),*tail=head;while(cur1&&cur2){if(cur1->val<=cur2->val){tail->next=cur1;cur1=cur1->next;}else{tail->next=cur2;cur2=cur2->next;}tail=tail->next;}//没有弄完的直接连接就行,后边都是串起来的if(cur1)tail->next=cur1;if(cur2)tail->next=cur2;tail=head->next;delete head;return tail;}
};
六、判断链表中是否有环
判断链表中是否有环_牛客题霸_牛客网
判环的经典思路就是快慢指针
class Solution {
public:bool hasCycle(ListNode *head) {ListNode*fast=head,*slow=head;while(fast&&fast->next){fast=fast->next->next;slow=slow->next;if(fast==slow)return true;}return false;}
};
七、链表中环的入口结点
链表中环的入口结点_牛客题霸_牛客网
class Solution {
public:ListNode* hasCycle(ListNode *head) {ListNode*fast=head,*slow=head;while(fast&&fast->next){fast=fast->next->next;slow=slow->next;if(fast==slow)return slow;}return nullptr;}ListNode* EntryNodeOfLoop(ListNode* pHead) {ListNode*cur1=hasCycle(pHead);if(cur1==nullptr)return nullptr;ListNode*cur2=pHead;while(cur1!=cur2){cur1=cur1->next;cur2=cur2->next;}return cur1;}
};
八、链表中倒数最后k个结点
链表中倒数最后k个结点_牛客题霸_牛客网
第一眼思路就是先遍历链表看有多少个结点,n,然后再次遍历,遍历到第n-k+1个结点,返回这个结点。其实就是官方解答的方法二
ListNode* FindKthToTail(ListNode* pHead, int k) {ListNode*cur=pHead;int n=0;while(cur){++n;cur=cur->next;}cur=pHead;int times=n-k;if(times<0)return nullptr;while(times--){cur=cur->next;}return cur;}
官方题解是快慢指针的思想,先让fast走k步,然后再一起走,fast=nullptr的时候slow到末尾就是k个结点,
class Solution {
public:ListNode* FindKthToTail(ListNode* pHead, int k) {ListNode* fast = pHead; ListNode* slow = pHead;//快指针先行k步for(int i = 0; i < k; i++){ if(fast != NULL)fast = fast->next;//达不到k步说明链表过短,没有倒数kelse return slow = NULL;//这种方式是先将NULL赋值给slow再返回slow}//快慢指针同步,快指针先到底,慢指针指向倒数第k个while(fast != NULL){ fast = fast->next;slow = slow->next;}return slow;}
};
九、删除链表的倒数第n个节点
删除链表的倒数第n个节点_牛客题霸_牛客网
还是找倒数第n个节点的问题,那么思路和上一题大差不差。
先用快慢指针写,因为要删除这个值,所以我们存一下slow前面的一个节点等会删掉slow
ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode*fast=head,*slow=fast;while(n--){if(fast)fast=fast->next;else return nullptr;}ListNode*newhead=new ListNode(-1),*prev=newhead;newhead->next=head;while(fast){fast=fast->next;slow=slow->next;prev=prev->next;}prev->next=slow->next;delete slow;prev=newhead->next;delete newhead;return prev;}
再用先找总结点个数的方法
ListNode* removeNthFromEnd(ListNode* head, int n) {//计算节点总个数ListNode*cur=head;int count=0;while(cur){++count;cur=cur->next;}int times=count-n;if(times<0)return nullptr;//创建虚拟头结点,cur从头开始ListNode*newhead=new ListNode(-1),*prev=newhead;newhead->next=head;cur=head;while(times--){cur=cur->next;prev=prev->next;}prev->next=cur->next;delete cur;prev=newhead->next;delete newhead;return prev;}
十、两个链表的第一个公共结点
两个链表的第一个公共结点_牛客题霸_牛客网
熟悉的题目,第一次见是在抖y上刷到,后来在别的平台写了一次,后面又在笔试训练的时候刷到。
class Solution {public:ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {if (!pHead1 || !pHead2) return nullptr;ListNode*cur1=pHead1,*cur2=pHead2;while(cur1!=cur2){cur1=cur1?cur1->next:pHead2;cur2=cur2?cur2->next:pHead1;}return cur1;}
};
十一、链表相加 II
链表相加(二)_牛客题霸_牛客网
其实就是高精度加法模拟,之前做过一道还帮我们逆转好了链表,这题还要我们自己逆转,真是可恶。不过链表逆转我们已经很熟悉了,高精度加法也刷过很多次了,无非就是1+1=2的问题。
class Solution {
public:ListNode* ReverseNode(ListNode*head){if(!head||!head->next)return head;ListNode* newhead=ReverseNode(head->next);head->next->next=head;head->next=nullptr;return newhead;}// ListNode* ReverseNode(ListNode*head)// {// ListNode*prev=nullptr;// ListNode*cur=head;// while(cur)// { // ListNode*t=cur->next;// cur->next=prev;// prev=cur;// cur=t;// }// return prev;// }ListNode* addInList(ListNode* head1, ListNode* head2) {//逆转链表ListNode*cur1=ReverseNode(head1),*cur2=ReverseNode(head2);int add=0;ListNode*newhead=new ListNode(-1);while(cur1||cur2||add){if(cur1){add+=cur1->val;cur1=cur1->next;}if(cur2){add+=cur2->val;cur2=cur2->next;}//头插ListNode*Node=new ListNode(add%10);Node->next=newhead->next;newhead->next=Node;add/=10;}ListNode*prev=newhead->next;delete newhead;return prev;}
};
十二、单链表的排序
单链表的排序_牛客题霸_牛客网
666昨天学校布置的作业就写到这个,刚好今天当作复习了
解法一:冒泡排序
链表的冒泡排序(真)不过这不符合题目要求这里我只是展示一下链表的冒泡如何操作
ListNode* sortInList(ListNode* head) {if(!head||!head->next)return head;ListNode* newhead=new ListNode(-1),*prev,*cur;newhead->next=head;bool flag;do{//更新flag!!!flag=false;prev=newhead;cur=head;//NextNode始终是cur下一个要比较的结点ListNode* NextNode=cur->next;while(NextNode){if(cur->val>NextNode->val){cur->next=NextNode->next;NextNode->next=cur;prev->next=NextNode;prev=NextNode;flag=true;}else {prev=cur;cur=cur->next;}NextNode=cur->next;}}while(flag);prev=newhead->next;delete newhead;return prev;}
虚假的链表冒泡排序,也就是只交换val
ListNode* sortInList(ListNode* head) {if(!head||!head->next)return head;ListNode*cur;bool flag;do{//更新flag!!!flag=false;cur=head;//NextNode始终是cur下一个要比较的结点ListNode* NextNode=cur->next;while(NextNode){if(cur->val>NextNode->val){int tmp=cur->val;cur->val=NextNode->val;NextNode->val=tmp;}cur=cur->next;NextNode=cur->next;}}while(flag);return head;}
解法二:归并排序(分治)
题目要求时间复杂度nlogn,那不就是分治了
创建快慢指针->分
排序->治
ListNode* merge(ListNode*pHead1,ListNode*pHead2){if(!pHead1)return pHead2;if(!pHead2)return pHead1;ListNode* head=new ListNode(0),*cur=head;while(pHead1&&pHead2){if(pHead1->val<pHead2->val){cur->next=pHead1;pHead1=pHead1->next;}else {cur->next=pHead2;pHead2=pHead2->next;}//注意移动指针!!!cur=cur->next;}if(pHead1)cur->next=pHead1;if(pHead2)cur->next=pHead2;cur=head->next;delete head;return cur;}ListNode* sortInList(ListNode* head) {if(!head||!head->next)return head;ListNode*left=head,*mid=head->next,*right=mid->next;while(right&&right->next){left=left->next;mid=mid->next;right=right->next->next;}//断开两个链表,分!left->next=nullptr;//不断在sortInList里分,分,分return merge(sortInList(head),sortInList(mid));}
解法三:转换为数组排序
说实话我感觉这种方法有点偷懒哈哈哈,最先想到的也是这个,果然大脑还是喜欢偷懒
ListNode* sortInList(ListNode* head) {vector<int> nums;ListNode*cur=head;//数据push进数组while(cur){nums.push_back(cur->val);cur=cur->next;}cur=head;sort(nums.begin(),nums.end());for(int i=0;i<nums.size();++i){cur->val=nums[i];cur=cur->next;}return head;}
十三、判断一个链表是否为回文结构
判断一个链表是否为回文结构_牛客题霸_牛客网
解法一:存进数组
也是借鉴了上一题,然后判断,不过感觉这样写很耍赖
bool isPail(ListNode* head) {vector<int> nums;ListNode*cur=head;while(cur){nums.push_back(cur->val);cur=cur->next;}for(int left=0,right=nums.size()-1;left<=right;++left,--right){if(nums[left]!=nums[right])return false;}return true;}
解法二:逆转链表+双指针
从中间位置断开,逆转后部分链表,然后双指针判断
或者像这样用头插实现逆转
bool isPail(ListNode* head) {ListNode*dummy=new ListNode(0),*cur1=head;while(cur1){ListNode*node=new ListNode(cur1->val);node->next=dummy->next;dummy->next=node;//不要忘记移动cur1!cur1=cur1->next;}ListNode*cur2=dummy->next;cur1=head;while(cur1){if(cur1->val!=cur2->val)return false;cur1=cur1->next;cur2=cur2->next;}return true;}
找中点,再逆序的写法:
ListNode* ReverseList(ListNode* head){if(!head||!head->next)return head;ListNode*newnode=ReverseList(head->next);head->next->next=head;head->next=nullptr;return newnode;}bool isPail(ListNode* head) {ListNode*slow=head,*fast=head;while(fast&&fast->next){slow=slow->next;fast=fast->next->next;}ListNode* ReverseHead=ReverseList(slow);ListNode*cur1=head,*cur2=ReverseHead;//奇偶统一,cur1&&cur2,有可能cur2先结束,所以要算上cur2while(cur1&&cur2){if(cur1->val!=cur2->val)return false;cur1=cur1->next;cur2=cur2->next;}return true;}
解法三:栈
先将数据全部存进栈,每次从栈顶取出的元素就是逆序的
bool isPail(ListNode* head) {stack<int> st;ListNode*cur=head;while(cur){st.push(cur->val);cur=cur->next;}cur=head;while(!st.empty()){if(st.top()!=cur->val)return false;st.pop();cur=cur->next;}return true;}
十四、链表的奇偶重排
链表的奇偶重排_牛客题霸_牛客网
朴素解法:
ListNode* oddEvenList(ListNode* head) {if(!head||!head->next||!head->next->next)return head;ListNode*cur=head,*newnode=new ListNode(0),*p=newnode;while(cur){ //不能直接指向cur1,要创建新结点p->next=new ListNode(cur->val);p=p->next;//cur->next不存在说明是尾部,那么得置为nullptr,//开始时没有做处理导致死循环了emmcur=cur->next?cur->next->next:nullptr;}cur=head->next;while(cur){p->next=new ListNode(cur->val);p=p->next;cur=cur->next?cur->next->next:nullptr;}p=newnode->next;delete newnode;return p;}
题解:相当于在原链表上直接修改指向
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;}
};
十五、删除有序链表中重复的元素
删除有序链表中重复的元素-I_牛客题霸_牛客网
法一:双指针
本来还写了排序的,后来发现题目里的链表是排好的,法科
#include <climits>
class Solution {
public:// ListNode*merge(ListNode*pHead1,ListNode*pHead2)// {// ListNode*newhead=new ListNode(0),*cur=newhead;// while(pHead1&&pHead2)// {// if(pHead1->val<pHead2->val)// {// cur->next=pHead1;// pHead1=pHead1->next;// }// else {// cur->next=pHead2;// pHead2=pHead2->next;// }// cur=cur->next;// }// if(pHead1)cur->next=pHead1;// if(pHead2)cur->next=pHead2;// cur=newhead->next;// delete newhead;// return cur;// }// ListNode* SortList(ListNode*head)// {// if(!head||!head->next)return head;// ListNode*left=head,*mid=head->next,*right=mid->next;// while(right&&right->next)// {// left=mid;// mid=right;// right=right->next;// }// left->next=nullptr;// return merge(SortList(head),SortList(mid));// }ListNode* deleteDuplicates(ListNode* head) {if(!head||!head->next)return head;//ListNode* NewHead=SortList(head);//cur存不重复的当前结点,next负责遍历ListNode*cur=head,*next=cur->next;while(next){if(next->val==cur->val)next=next->next;else{cur->next=next;cur=next;next=cur->next;}}//断开后续所有结点cur->next=nullptr;return head;}
};
解法二:单指针简单遍历即可
官方代码用单指针也可以实现:推荐
ListNode* deleteDuplicates(ListNode* head) {if(!head||!head->next)return head;ListNode*cur=head;while(cur&&cur->next){if(cur->val==cur->next->val){//跳过cur->nextcur->next=cur->next->next;}//遇到不等的了,说明当前和cur相等的val已经全部跳过//cur->next存的是下一个要处理的val,更新curelse cur=cur->next;}return head;}
十六、删除有序链表中重复的元素II
删除有序链表中重复的元素-II_牛客题霸_牛客网
这次出现次数大于1的直接被删了,就像玩开心消消乐那样
现在就是如果cur->next的值与cur的值相等,那么cur是被跳过的那个,如果开头第一个元素就是跳过的,为了方便操作,我们引入虚拟头结点。思路重在修改指向。
ListNode* deleteDuplicates(ListNode* head) {if (!head || !head->next)return head;//创建新表头,直接从新表头开始修改方向ListNode* newhead = new ListNode(0), *cur = newhead;newhead->next = head;while (cur ->next&& cur->next->next) {//相等跳过这一整段if (cur->next->val == cur->next->next->val) {int tmp=cur->next->val;while (cur->next && cur->next->val==tmp) {cur ->next=cur->next->next;}}//不等的表示符合,cur直接找下一个结点else {cur=cur->next;}}cur= newhead->next;delete newhead;return cur;}
链表结束。