当前位置: 首页 > news >正文

牛客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;}

链表结束。

http://www.dtcms.com/a/507069.html

相关文章:

  • 量化策略中三周期共振策略的仓位管理方法
  • 【python】快速实现pdf批量去除指定位置水印
  • 在 macOS 上用 Docker 为 Java 后端 常见开发需求搭建完整服务(详尽教程)
  • 网站建设翻译网站添加二维码
  • Debug —— Docker配置镜像后下载Mysql等报连接超时
  • 中冶交通建设集团网站发网站视频做啥格式最好
  • 软件定制一条龙整站多关键词优化
  • 【Vscode】显示多个文件 打开多个文件时实现标签栏多行显示
  • vue 技巧与易错
  • vscode编写Markdown文档
  • 使用VScode 插件,连接MySQL,可视化操作数据库
  • 基于微信小程序的公益捐赠安全平台9hp4t247 包含完整开发套件(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
  • 【论文精读】FlowVid:驯服不完美的光流,实现一致的视频到视频合成
  • 【C++】滑动窗口算法习题
  • C语言趣味小游戏----扫雷游戏
  • 三款AI平台部署实战体验:Dify、扣子与BuildingAI深度对比
  • 网站制作难不难小红书搜索优化
  • Python如何使用NumPy对图像进行处理
  • 房产中介网站开发站长工具之家
  • Linux服务器编程实践60-双向管道:socketpair函数的实现与应用场景
  • c++结构体讲解
  • 青岛商城网站建设网站相互推广怎么做
  • Linux学习笔记(九)--Linux进程终止与进程等待
  • 虚幻引擎5 GAS开发俯视角RPG游戏 P06-09 玩家等级与战斗接口
  • JavaSE内容梳理与整合
  • JavaScript日期处理:格式化与倒计时实现
  • 网页与网站设计 什么是属性网站开发用的框架
  • 长沙正规网站建设价格公司概况简介
  • STM32卡尔曼滤波算法详解与实战应用
  • 【自适应粒子滤波 代码】Sage Husa自适应粒子滤波,用于克服初始Q和R不准确的问题,一维非线性滤波。附有完整的MATLAB代码