前言:
本篇针对链表的一些场用题目做一个汇总和总结,链表的场用操作如删除、合并、插入、查找、排序等等,善于使用头结点dump来定位,游走节点temp来操作,一些链表题目经常可以用用到递归的思路返回,一般是head->next后接递归等等,同时注意到链表中每个元素是可以存入哈希表的,他们和数字一样也存在唯一性!
1.Leetcode21 合并两个有续链表
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode * dump=new ListNode(-1);
ListNode* pre=dump;
while(l1!=nullptr && l2!=nullptr){
if(l1->val<=l2->val){
pre->next=l1;
l1=l1->next; //没事 这个l1等于l1的next保证了后面的元素不会丢失,我已经next了 相当于位置索引,之前在新链表的元素势必会给他一个归属的!没事就是说他的next会安排
}
else{
pre->next=l2;
l2=l2->next;
}
pre=pre->next;
}
pre->next= l1==nullptr ? l2:l1;
return dump->next;
}
};
合并两个有续链表的另外一种方法是通过建立新的链表,不要把链表看成地址,而是看成一个结构体存着下次的地址,他们整体构成了一个数组而已嘛。所以可以在vector或者栈、队列中存他们本身,或者存他的val,然后根据val来造一个新的“数组”就是链表。有些是在原来链表上操作,相当于把数组顺序换下。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
priority_queue<int,vector<int>,greater<int> >q;
while(l1){
q.push(l1->val);
l1=l1->next;
}
while(l2){
q.push(l2->val);
l2=l2->next;
}
/
ListNode* dump=new ListNode(-1);
ListNode* temp=dump;
while(!q.empty()){
temp->next=new ListNode(q.top());
q.pop();
temp=temp->next;
}
return dump->next;
}
};
在这里插入代码片
2.Leetcode203 移除链表元素
题目: 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
思路: 注意到链表的删除操作通常需要通过他的前一个结点辅助操作,如temp->next=temp->next->next. 假设要删除的元素在第一个,我们必须要制造一个dump头结点指向head。同时dump->next也给我们最终返回找到方向。所以dump不能移动,再生成一个temp=dump来移动操作即可。循环过程中注意到我们对初始点开始判断肯定是while(temp->next)但是是不是每一次temp都要移动呢,如果删除元素了,然后temp当前值是null,你再判断temp的next会出错。所以删除了就不移动。不删除那说明next没问题移动。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dump=new ListNode(0);
dump->next=head;
ListNode* temp=dump;
while(temp->next){
if(temp->next->val==val)
temp->next=temp->next->next;
else
temp=temp->next;
}
return dump->next;
}
};
此外,链表的题目通常用递归来解决,从后往前面判断返回,思路就是如果等于当前如果head和val相同就返回下一个节点,不然就返回当前的,然后下一个节点我也做同样的操作,注意操作后是返回给上一层,上一层属于head的next,所以有递归的趋势,就在前面增加一个head->next=。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if (head == nullptr) {
return head;
}
head->next = removeElements(head->next, val);
return head->val == val ? head->next : head;
}
};
3.Leetcode 160:相交链表
题目: 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
方法1:哈希集合
判断两个链表是否相交,可以使用哈希集合存储链表节点。
首先遍历链表 \textit{headA}headA,并将链表 \textit{headA}headA 中的每个节点加入哈希集合中。
然后遍历链表 \textit{headB}headB,对于遍历到的每个节点,判断该节点是否在哈希集合中:
如果当前节点不在哈希集合中,则继续遍历下一个节点;
如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分,因此在链表 \textit{headB}headB 中遍历到的第一个在哈希集合中的节点就是两个链表相交的节点,返回该节点。
如果链表 \textit{headB}headB 中的所有节点都不在哈希集合中,则两个链表不相交,返回 NULL
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(!headA || !headB)
return NULL;
unordered_set<ListNode*> visited;
ListNode* temp=headA;
while(temp!=NULL){
visited.insert(temp);
temp=temp->next;
}
temp=headB;
while(temp!=NULL){
if(visited.count(temp))
return temp;
temp=temp->next;
}
return NULL;
}
};
方法2:双指针,就是两个指针分别走,走到链表头走另外一条链表,走到交点时候步数一样,所以等链表位置相同了,就返回。注意到链表的元素是否相同不是看val 直接拿出来比较就行,这是包含地址的比较! 放到哈希表里面也是,这是链表中的一个元素,包含地址,和元素值!
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr) {
return nullptr;
}
ListNode *pA = headA, *pB = headB;
while (pA != pB) {
pA = pA == nullptr ? headB : pA->next;
pB = pB == nullptr ? headA : pB->next;
}
return pA;
}
};
方法3:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode*dumpA=headA;
ListNode*dumpB=headB; int len1=0,len2=0;
while(dumpA || dumpB){
if(dumpA){
len1++;
dumpA=dumpA->next;
}
if(dumpB){
len2++;
dumpB=dumpB->next;
}
}
int diff=len1-len2;
if(diff>0){
dumpA=headA;
dumpB=headB;
for(int i=0;i<diff;i++)
dumpA=dumpA->next;
while(dumpA){
if(dumpA==dumpB)
return dumpA;
dumpA=dumpA->next;
dumpB=dumpB->next;
}
}
else{
dumpA=headA;
dumpB=headB;
for(int i=0;i<-diff;i++)
dumpB=dumpB->next;
while(dumpB){
if(dumpB==dumpA)
return dumpB;
dumpA=dumpA->next;
dumpB=dumpB->next;
}
}
return nullptr;
}
};
4.面试题 02.05. 链表求和
题目:
给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。
思路:
一开始想用栈来做,然后把数字都取出来算出来后返回,后来在最后几个测试中失败了,数字太长了,无语!!!!直说不让取得了,那就不能取出来了。下面附上取出来的操作:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
//用栈先入后出
stack<ListNode*>stack1;
int count2=0;
while(l1){
stack1.push(l1);
l1=l1->next;
}
while(l2){
stack1.push(l2);
l2=l2->next;
count2++;
}
long sum2=0;
int size=stack1.size();
for(int i=1;i<=count2;i++){
sum2=sum2*10+stack1.top()->val;
stack1.pop();
}
long sum1=0;
for(int i=1;i<=size-count2;i++){
sum1=sum1*10+stack1.top()->val;
stack1.pop();
}
long sum=sum1+sum2;
//新链表
ListNode* newlist=new ListNode;
ListNode* temp=newlist;
if(sum==0){
temp->next=new ListNode;
temp->next->val=0;
}
while(sum){
temp->next=new ListNode;
temp->next->val=sum%10;
temp=temp->next;
sum=sum/10;
}
return newlist->next; //每次都只对next操作就行了。
}
};
好,既然不能取出来,那就直接操作,这个数字是倒着放的,我们直接按照加法规律每次两个链表都往前进,对当前位计算加法值然后赋值给新链表。递归下一个位置,传入count进位,直到两个链表都为null,且进位为0即可。注意到链表题目的递归一般都是res->next=递归表达式,每次对当前值赋值最后return res;
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
return addTwoNumbersCore(l1, l2, 0);
}
ListNode* addTwoNumbersCore(ListNode* l1, ListNode* l2, int carry) {
if (!l1 && !l2 && carry == 0) {
// 当输入的节点均为null且无需处理进位时,结束
return nullptr;
}
int val = carry + (l1 ? l1 -> val : 0) + (l2 ? l2 -> val : 0); // 计算当前的和
auto res = new ListNode(val % 10);
res -> next = addTwoNumbersCore((l1 ? l1 -> next : nullptr), (l2 ? l2 -> next : nullptr), val / 10);
return res;
}
};
==面试题02.04.分割链表 ==
题目:
编写程序以 x 为基准分割链表,使得所有小于 x 的节点排在大于或等于 x 的节点之前。如果链表中包含 x,x 只需出现在小于 x 的元素之后(如下所示)。分割元素 x 只需处于“右半部分”即可,其不需要被置于左右两部分之间。
思路:
注意到这种题目,相当于对链表做个自定义的排序。通常我们需要借助其他的手段对链表做处理,最常见的操作是生成指向链表头部的节点,用这个来不断改变链表的结构产生几个字链表。
这道题目可以采用模拟的方法,我们可以创建两个dump节点,分别用来连接val值大于等于目标x的链表元素和val值小于目标值x的元素。然后将这两个子链表的头尾相连即可。注意到在连接过程中我们需要规避一个链表出现两次连接方向,或者说链表连错错乱一个链表元素收到两次的连接。因为我们的双链表元素是原始链表的完备情况,所以不存在漏解,而且我们应该就是确定了某个元素是这条链表的,那这条链表的next指向它,什么意思?注意到这条链表的已有元素本身存在一个指向,我现在改变他的指向了。但是注意到 尽管中间过程可以改变他的指向,但是每条链表的最后一个元素,他的指向我们没改变!! 所以两条链表的next都要给他给null。new的产生只需要在刚开始。
而且有人问为什么用头结点不直接产生一个节点呢。因为我们的初衷是这个链表节点判断可行我们加入。然后链表要移动!移动! 如果直接是small=temp,那small的下一个位置如何确定呢,循环中无法给出!所以一开始就是small的next,然后不断让small回到已经完备的位置,每次都很安心。如果循环中是temp的next,那开始的节点怎么办呢 要处理啊。包括这篇文章的第二个问题,循环中也是原始链表的第一个,只不过用dump的next!
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
//用模拟的思路,遍历原始链表
//创建两个新链表,一个存比目标值小于等于的,一个存比目标值大的,最后合并就行了。
ListNode* small=new ListNode;
ListNode* large=new ListNode;
ListNode* sm1=small;
ListNode* lar1=large;
ListNode* temp= new ListNode;
temp=head;
while(temp){
if(temp->val<x){
small->next=temp;
small=small->next;
}
else{
large->next=temp;
large=large->next;
}
temp=temp->next;
}
// 合并
large->next=NULL;
small->next=NULL;
small->next=lar1->next;
return sm1->next;
}
};
—————————————————————————————————————————————————————————————
NC50 链表中的节点每k个一组翻转
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(head==nullptr || !head->next || k<2) return head;
stack<ListNode*>st;
ListNode* cur=head;
ListNode* tmp=new ListNode(0);
ListNode* node=tmp;
while(head!=nullptr)
{
cur=head;
int i=k;
while(i>0 && head!=nullptr)
{
st.push(head);
head=head->next;
i--;
}
if(st.size()==k)
{
while(!st.empty())
{
tmp->next=st.top();
st.pop();
tmp=tmp->next;
tmp->next=nullptr; //增加一行!!
}
}
else
{
tmp->next=cur;
break;
}
}
return node->next;
}
};
class Solution {
public:
/// 参考翻转pairs,翻转x~x+(k-1)之间的节点, x->next = reverseKGroup(x+k,k)
ListNode* reverse(ListNode *first,ListNode *last)
{
ListNode *pre = nullptr;
while(first!=last)
{
ListNode *temp = first->next;
first->next = pre;
pre = first;
first = temp;
}
return pre;
}
ListNode *reverseKGroup(ListNode *head, int k)
{
if(!head)
return nullptr;
ListNode *node = head;
for(int i=0;i<k;i++)
{
if(!node)
return head;
node = node->next;
}
ListNode *newHead = reverse(head,node);
head->next = reverseKGroup(node,k);
return newHead;
}
};