链表
在本文中,我将介绍几道链表相关的题目,由浅入深,有些题也比较巧妙,可以学到一些好的思想和解题方法。
Leetcode21 合并两个升序链表
将两个升序链表合并成一个新的升序链表并返回。
代码部分
ListNode *MergetwoList(ListNode*head1,ListNode*head2)
{
ListNode*dummy=new ListNode(0);
ListNode*p=dummy;
while(head1 && head2)
{
if(head1->val<head2->val)
{
p->next=new ListNode(head1->val);
head1=head1->next;
}
else
{
p->next=new ListNode(head2->val);
head2=head2->next;
}
p=p->next;
}
p->next=head1?head1:head2;
return dummy->next;
}
- 若head1和head2均不空,则取较小的,之后直接p->next=head1?head1:head2把剩下的那部分接过来
Leetcode 147 对链表进行插入排序
由于链表本身就是连着的,对其进行插入排序需要重点考虑的是一些指针应该怎么变换
ListNode *insertionSortlist(ListNode*head)
{
if(head==nullptr) {return nullptr;}
ListNode*dummyHead=new ListNode(0);
dummyHead->next=head;
ListNode*lastSort=head;
ListNode*curr=dummyHead->next->next;
while(curr)
{
if(lastSort->val<curr->val) {lastSort=lastSort->next;}
else
{
ListNode*pre=dummyHead;
while(pre->next->val<curr->val)
{
pre=pre->next;
}
lastSort->next=curr->next;
curr->next=pre->next;
pre->next=curr;
}
curr=lastSort->next;
}
return dummyHead->next;
}
- 此方法中用到了三个指针,一个是curr,就是目前该排序的节点,一个是lastSort,代表着在这个指针之前的已经是已序的了,还有是一个pre,他的作用是为了帮助curr,找到他应该放到哪里。
- 不好整的就是找到这个节点该插到哪里后,这些指针的指向应该怎么变化。这里我总结了一个小方法:从前向后看,正确的状态应该是pre->curr,curr->pre->next,lastSort->next=curr->next。那么写code的时候就从后往前写,先有lastSort->next=curr->next,再curr->next=pre->next,最后pre->next=curr,这样就不会有野指针出现了(应该是这样,我也不是百分之百确定,百分之九十九确定)。
Leetcode23 合并K个升序链表
在上上节中介绍了一个桶排序,当时是把数值分配到了若干个桶中,将每个桶中的数值排序后再进行合并,其中每个桶中排序好的数据就是一个升序链表,当时合并的方法比较简单粗暴,就是1与2先合并,然后再与3合并,依次类推。
这里介绍一种分治合并,和归并排序的思想有点像,先两两合并,合并后链表的长度减半,再两两合并,直到最终只有一个链表。
ListNode *mergeTwoLists(ListNode*head1,ListNode*head2)
{
ListNode*dummy=new ListNode(0);
ListNode*p=dummy;
while(head1 &&head2)
{
if(head1->val<head2->val)
{
p->next=new ListNode(head1->val);
head1=head1->next;
}
else
{
p->next=new ListNode(head2->val);
head2=head2->next;
}
p=p->next;
}
p->next=head1?head1:head2;
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)
{
return merge(lists,0,lists.size()-1);
}
Leetcode141 环型链表
该题有两种比较合适的解法
- 快慢指针,一个指针一次走一步,另一个指针一次走两步,如果链表中有环的话,那么一定会在环中的某个结点相遇
- 哈希表,从链表的头结点开始遍历,把遍历过的节点均放到哈希表中,如果是循环链表的话,那么哈希表中就一定会出现重复的节点。
1.快慢指针法:
bool CycleList(ListNode*head)
{
if(head==nullptr || head->next==nullptr)
{
return false;
}
ListNode*slow=head;
ListNode*fast=head->next;
while(slow!=fast)
{
if(fast==nullptr || fast->next==nullptr)
{
return false;
}
slow=slow->next;
fast=fast->next->next;
}
return true;
}
2.哈希表法
bool hasCycle(ListNode*head)
{
unordered_set<ListNode*>seen;
while(head)
{
if(seen.count(head)) return true;
seen.insert(head);
head=head->next;
}
return false;
}
leetcode160 相交链表
给你两个但链表的头结点headA和headB,请你找出并返回两个单链表相交的起始节点,两个链表不存在相交节点,返回null
- 很明显,此题如果要是用哈希表的话直接就秒了,先把第一个链表遍历一遍,把所有的节点都装到哈希表,再遍历另一个链表,如果其中的节点在哈希表中是存在的,那么就是相交链表。
- 还有一种非常巧妙的双指针的方法,A链表表示成a+c,B链表表示成b+c,一个指针先把A遍历一遍后再去遍历B,另一个先把B遍历一遍后再去遍历A,这样一定会在走了a+b+c步之后相遇(如果两个链表没有相交的部分则可以把两个链表理解成a+nullptr,b+nullptr,他们在走了a+b步之后都是nullptr)。
ListNode*getIntersectionNode(ListNode *headA, ListNode *headB)()
{
if(headA==nullptr ||headB==nullptr) return nullptr
ListNode*pA=headA;
ListNode*pB=headB;
while(pA!=pB)
{
pA=pA==nullptr?headB:pA->next;
pB=pB==nullptr?headA:pB->next;
}
return pA;
}
用一句话来形容这个方法:我走过你走过的路,只为与你相拥。
Leetcode19 删除链表中的倒数第N个节点
给你一个链表,删除链表的倒数第n个节点,并返回头结点。
- 一种比较自然的想法是先看看链表中一共有多少节点(m),删除倒数第n个也就意味着是正数第(m-n+1)个
- 另一种方法自然就是双指针了,双指针在链表中的运用比较多。先设置一个dummy,指向head,让两个指针(p1,p2)指向dummy。先让p1走n步,然后两个指针再一起走,p1指向最后一个节点的时候,p2->next就是要删除的节点了。
ListNode* removeNthFromEnd(ListNode* head, int n)
{
ListNode *dummy=new ListNode(0);
dummy->next=head;
ListNode*p1=dummy;
ListNode*p2=dummy;
for(int i=0;i<n;i++)
{
p1=p1->next;
}
while(p1->next)
{
p1=p1->next;
p2=p2->next;
}
p2->next=p2->next->next;
return dummy->next;
}
Leetcode876链表的中间节点
给你一个单链表,请你找出并返回链表的中间节点,如果有两个中间节点,则返回第二个。
- 用快慢指针的方法,一个指针一次走一步,另一个指针一次走两步。
ListNode *middle(ListNode*head)
{
ListNode *p1=head;
ListNode *p2=head;
while(p2 &&p2->next)
{
p1=p1->next;
p2=p2->next->next;
}
return p1;
}
如果对于有两个中间节点的链表,我想返回第一个应该怎么办?仔细观察一下,我们只要更改一下while循环中的条件即可,换成(p->next &&p->next->next)。
Leetcode143重排链表
给定一个单链表L的头结点head,单链表L表示为:
$$
L_1->L_2->…->L_{n-1}->L_{n}
$$
请将其重新排列后变为:
$$
L_0->L_n->L_1->L_{n-1}->L_2->L_{n-2}->…
$$
不能只是单纯的改变结点内部的值,而是需要世界的进行结点交换。
- 把原来的链表改成前面取一个后面取一个,以此类推。先找到链表的中间节点,将链表分成两部分,把后面的那部分链表反转一下,然后再合并两个链表。此题步骤相对较多,可以帮助巩固一些基本的操作。
ListNode *middleNode(ListNode*head)
{
ListNode*slow=head;
ListNode*fast=head;
while(fast->next && fast->next->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
ListNode*reverseList(ListNode*head)
{
ListNode*prev=nullptr;
ListNode*curr=head;
while(curr)
{
prev=new ListNode(curr->val,prev);
curr=curr->next;
}
return prev;
}
void mergeList(ListNode*l1,ListNode*l2)
{
ListNode*l1_tmp;
ListNode*l2_tmp;
while(l1!=nullptr && l2!=nullptr)
{
l1_tmp=l1->next;
l2_tmp=l2->next;
l1->next=l2;
l1=l1_tmp;
l2->next=l1;
l2=l2_tmp;
}
}
void reorderList(ListNode*head)
{
if(head==nullptr)
{
return;
}
ListNode*mid=middleNode(head);
ListNode*l1=head;
ListNode*l2=mid->next;
mid->next=nullptr;
l2=reverseList(l2);
mergeList(l1,l2);
}
Leetcode2 两数相加
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头
示例:
$$
2->4->3 \
5->6->4 \
7->0->8 \
$$
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
ListNode *Hwikb=new ListNode(0);
ListNode *current=Hwikb;
int flag=0;
while(l1 ||l2||flag)
{
int sum=0;
sum+=flag;
if(l1)
{
sum+=l1->val;
l1=l1->next;
}
if(l2)
{
sum+=l2->val;
l2=l2->next;
}
flag=sum/10;
current->next=new ListNode(sum%10);
current=current->next;
}
return Hwikb->next;
}
专门为大于10的节点立了一个flag,相当巧妙。

