今日为大家分享一波关于链表中的一些题目!希望读完本篇文章,读者们也可以尝试一下这些题目,也会加强大家对链表的认识!
445. 两数相加 II
1.算法思路:
通过题目的讲解,以及用例的分析,我们可以发现,链表是以高位向低位进行连接的!而我们的加法操作都是低位相加的,所以我们只需要进行将原链表进行逆置一下,然后进行相加,得到相加后的结构再逆置一下即可。看下图:
所以我们的思路就很简单,逆置原链表然后相加再逆置即可!需要注意的是:我们需要用一个数字来统计我们的进位!当进位为0且俩链表都走到空时的时候结束即可!进行逆置链表的时候,我们使用头插法即可!
2.代码如下:
Node* reverse(Node* head)
{
Node *Head=new Node(0);
Node*pre=Head;
Node*cur=head;
while(cur)
{
Node* next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
delete Head;
return pre;
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
Node*newhead1=reverse(l1);
Node*newhead2=reverse(l2);
Node*ret=new Node(0);
Node*pre=ret;
int add=0; //代表的是进位!
while(newhead1||newhead2||add)
{
if(newhead1)
{
add+=newhead1->val;
newhead1=newhead1->next;
}
if(newhead2)
{
add+=newhead2->val;
newhead2=newhead2->next;
}
pre->next=new Node(add%10);
add/=10;
pre=pre->next;
}
//首先实现的是逆置两个链表!
return reverse(ret->next);
}
};
做过本道题,我们就可以尝试一下这道两数相加的题目,这道题目我们不需要进行逆置,直接进行相加处理好进位即可!希望大家下来可以尝试做一下!
2. 两数相加
24. 两两交换链表中的节点
1.算法讲解:
对于链表的题目,我们大多都是需要加上哨兵位的,因为加上哨兵位可以大大的减少题目的难度,下面我通过画图给大家分析一波如何求解这道题!
我们发现经过一次交换需要改变三个指针的指向,防止后面因为顺序问题,导致节点更改错误,我们提前将需要进行更改的节点进行存储下来即可!
下面就是修改结点指针的逻辑!
pre->next=next;
next->next=cur;
cur->next=nnext;
下一次我们再进行交换的时候,需要将cur节点进行更换到nnext上,pre更换成上一次的cur中,先将pre进行更改,再进行更改cur!这个顺序不能颠倒,一旦颠倒就会出错!
这是偶数个节点的情况,当节点的个数为奇数个的时候,假设没有4这个节点,我们就只需要进行一次两两交换即可!所以执行交换的逻辑是cur和next都不为空才进行交换!一个为空就不需要进行交换!
2.代码:
下面我把代码贴在下面:
21. 合并两个有序链表
1.算法讲解:
本题的解法就是首先设置一个哨兵位,然后将较小的元素进行尾插即可!本题难度不大,就不详细画图讲解了。
2.代码
class Solution {
public:
typedef ListNode Node;
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2)
{
//设置一个哨兵位 然后两个节点比较大小,小的直接插入即可!
Node*newhead=new Node(0);
Node*pre=newhead;
while(list1&&list2)
{
if(list1->val<=list2->val)
{
//先将list1进行插入!
pre->next=list1;
pre=list1;
list1=list1->next;
}
else
{
pre->next=list2;
pre=list2;
list2=list2->next;
}
}
if(list1)
{
pre->next=list1;
}
if(list2)
{
pre->next=list2;
}
pre=newhead->next;
delete newhead;
return pre;
}
};
LCR 078. 合并 K 个升序链表
本题要合并的是k个升序链表,我将通过递归与非递归的两种方式进行讲解!
1.非递归解法:
1.算法思路:
对于非递归的方法,我们采用优先级队列的方法进行求解,我们每次求出较小的那个值,然后进行尾插即可!我们要自己建立一个优先级队列,对于比较函数,因为我们要排的升序,所以当bool为true的时候,进行向下调整即可!仿函数的写法如下:
仿函数写好之后,我们将vector中的所有元素push到我们的优先级队列中即可!然后进行出队操作,每当出队的时候,我们出的是一个指针,如果该指针下面还有节点的话,我们还需要将其后继节点push到队中,当队为空的时候,我们的链表也就链接好了!
2.代码如下:
class Solution
{
public:
//写一个仿函数用于比较节点内的值!
struct cmp
{
bool operator()(const ListNode*l1,const ListNode*l2)
{
return l1->val>l2->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists)
{
priority_queue<ListNode*,vector<ListNode*>,cmp> heap;
for(auto &l:lists)
{
//将所有元素插入到堆内!
if(l)
{
heap.push(l);
}
}
//下面判断堆是否为空 堆为空就结束即可!
ListNode*head=new ListNode(0);
ListNode*pre=head;
while(!heap.empty())
{
ListNode*t=heap.top();
if(t->next)
{
heap.push(t->next);
}
heap.pop();
pre->next=t;
pre=t;
}
pre=head->next;
delete head;
return pre;
}
};
2.递归解法
1.算法思路:
对于递归而言,递归的思路就是将一个大问题转化为无限多个小问题!下面简单画一下递归处理的图,就可以理解每一步做的工作都是相同的,所以我们可以使用递归的思路进行求解!
从这个抽象图中可以看出,我们可以一直将链表进行划分,直至不能划分为两个或只有一个为止,然后我们就可以将问题转化为合并两个有序链表的问题了!其实就是类似于归并排序的思想!
2.代码如下:
class Solution {
public:
//合并两个有序链表的逻辑!
ListNode* mergesort(ListNode*&l1,ListNode*&l2)
{
//处理一下特殊情况!
if(l1==nullptr)
{
return l2;
}
if(l2==nullptr)
{
return l1;
}
ListNode*Head=new ListNode(0);
ListNode*pre=Head;
while(l1&&l2)
{
if(l1->val<=l2->val)
{
pre->next=l1;
pre=l1;
l1=l1->next;
}
else
{
pre->next=l2;
pre=l2;
l2=l2->next;
}
}
//处理剩下的元素!
if(l1)
{
pre->next=l1;
}
if(l2)
{
pre->next=l2;
}
pre=Head->next;
delete Head;
return pre;
}
//此函数的作用是将k个链表进行划分,直到不能再划分即可!
ListNode*merge(vector<ListNode*>& lists,int left,int right)
{
if(left>right) return nullptr;
if(left==right) return lists[left];
//下面开始划分区域,一直进行划分即可!
int mid=(left+right)/2;
ListNode* l1=merge(lists,left,mid);
ListNode* l2=merge(lists,mid+1,right);
//走到这里说明无法再进行划分链表了,这时我们就开始实现合并两个有序链表即可
return mergesort(l1,l2);
}
ListNode* mergeKLists(vector<ListNode*>& lists)
{
//也可以采用递归的思路进行求解!
//递归就是将一个大问题划分为若干个小问题,然后一一进行求解即可!
return merge(lists,0,lists.size()-1);
}
};
LCR 026. 重排链表
1.算法讲解:
通过题目分析,我们可以得出本题其实分为三大步骤!
1.找到中间节点!
2.逆置链表!
3.合并两个链表!
所以我们先找到中间节点,然后从中间节点为结束,分为两个链表,然后将中间节点的后一个链表进行逆置即可!至于为什么要逆置中间节点的下一个节点,而不是中间节点,我们当逆置中间节点的时候我们会存在很多特殊情况,但是对于其下一个节点就不会存在那么多问题,感兴趣的伙伴们可以下来尝试一下!下面我把代码贴在下面!
2.代码如下
class Solution {
public:
typedef ListNode Node;
Node*Midnode(Node*head)
{
Node *fast=head;
Node* slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
Node*Rerverse(Node*head)
{
Node *cur=head;
Node*Head=nullptr;
while(cur)
{
Node* next=cur->next;
cur->next=Head;
Head=cur;
cur=next;
}
return Head;
}
void reorderList(ListNode* head)
{
//求出中间节点!!
Node*Mid=Midnode(head);
//讲这个链表分为两段 分别为从头到中间节点!
//断开分成两个链表!
Node*list2=Mid->next;
Node*list2head=Rerverse(list2);
Node*list1=head;
Mid->next=nullptr;
//将链表二进行逆置 然后进行合并即可!!
Node*ret=new Node(0,nullptr);
Node*cur1=list1;
Node*cur2=list2head;
Node*pre=ret;
while(cur1&&cur2)
{
if(cur1)
{
pre->next=cur1;
pre=cur1;
cur1=cur1->next;
}
pre->next=cur2;
pre=cur2;
cur2=cur2->next;
}
pre->next=cur1;
delete ret;
}
};
25. K 个一组翻转链表
1.算法思路:
我们只需要进行统计节点的个数,然后再求出需要翻转的次数,进行头插法一一进行链接,最后将没有遍历到元素进行链接即可!!
2.代码如下:
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k)
{
//需要判断翻转几组,然后对这几组中的节点进行翻转!
int count=0;
ListNode* cur=head;
//统计节点的个数!
while(cur)
{
++count;
cur=cur->next;
}
//记录总共需要翻转几组!!
int n=count/k;
//然后使用头插法进行翻转即可!
//需要注意的是,翻转之后需要记录之前的第一个节点,因为下一组需要与之前的第一个节点连接!
ListNode*Head=new ListNode(0);
ListNode*pre=Head;
cur=head;
for(int i=0;i<n;i++)
{
//tmp用于记录每组的第一个节点 因为每组的第一个节点需要与下一组的节点相连!
ListNode* tmp=cur;
for(int j=0;j<k;j++)
{
//此处进行头插操作即可!
ListNode*next=cur->next;
cur->next=pre->next;
pre->next=cur;
cur=next;
}
pre=tmp;
}
//接下来就是处理没有遍历到的元素!
pre->next=cur;
cur=Head->next;
return Head->next;
}
};