文章目录
题目速览
题目一
题目二
题目三
题目四
题目五
题目六
题目七
题目八
题目九
题目十
题目十一
题目十二
一:删除结点中给定值的所有结点
法一:构建头结点法
此题所给出的是一个没有头结点的链表,没有头结点的链表在操作时对于第一个结点的处理非常苦难,要考虑很多种情况,所以我们可以自己造一个头结点,最终返回的时候再head放到该放到的位置上。这种方法其实有点作弊的嫌疑,但是无疑是一种非常好的方法
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* dummy=(struct ListNode*)malloc(sizeof(struct ListNode));//制造头结点
dummy->next=head;//头结点下一个就是第一个元素
dummy->val=NULL;//头结点不含数据元素
struct ListNode* pre=dummy;
struct ListNode* cur=head;//双指针法
while(cur!=NULL){
if(cur->val!=val)
{
cur=cur->next;
pre=pre->next;
}else
{
pre->next=cur->next;
cur=cur->next;
}
}
head=dummy->next;//一定要把头指针找回
free(dummy);
return head;
}
法二:迭代法
这种方法要注意一点,由于pre指针在初始状态下是空,cur指针首先指向head,所以如果第一个结点就是要删除的结点,那么进行删除操作时“pre->next=cur->next”的操作显然就是有问题的,所以说对于这种情况要特殊处理
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* pre=NULL;
struct ListNode* cur=head;
while(cur!=NULL)//cur进行扫描
{
if(head->val==val)//特殊情况,第一个结点就是要删除的结点
{
head=cur->next;//head向后走
free(cur);
cur=head;//一定注意这一点,因为没有这句cur就成了野指针了
}
else//一般情况
{
if(cur->val==val)//所指结点就是要删除的结点
{
pre->next=cur->next;
free(cur);
cur=pre;//一定注意这一点,因为没有这句cur就成了野指针了
}
else//不是就迭代
{
pre=cur;
cur=cur->next;
}
}
}
return head;
}
二:反转单链表
法一:迭代法
使用迭代的方法逆置链表,需要用到三个指针
struct ListNode* reverseList(struct ListNode* head)
{
//递归
/*if (head == NULL || head->next == NULL)
{
return head;
}
struct ListNode* newHead = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return newHead;
*/
struct ListNode* pre=head;
struct ListNode* cur=NULL;
struct ListNode* save;
while(pre!=NULL)
{
save=pre;
pre=pre->next;
save->next=cur;
cur=save;
}
return cur;
}
法二:头插法
把原来的结点取下来,然后进行头插
struct ListNode* newHead=NULL;
struct ListNode* cur=head;
while(cur != NULL)
{
head=cur;
cur=cur->next;
head->next=newHead;
newHead=head;
}
return newHead;
三:取链表的中间结点
法一:普通解法
这种方法是最容易想到的方法,首先遍历链表,得到总的结点数,然后找出中间节点,再次遍历即可。
int count=0;
struct ListNode* cur=head;
while(cur!=NULL)
{
count++;
cur=cur->next;
}
int ret=count/2;
cur=head;
while(ret)
{
cur=cur->next;
ret--;
}
return cur;
法二:双指针法
一般来说,遍历一次或者稍加巧妙的方法,就可以用到双指针。定义一个慢指针和快指针,快指针每次走两步,慢指针每次走一步,最后慢指针所指就是中间节点
struct ListNode* slow=head;
struct ListNode* fast=head;
while(fast->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
if(fast==NULL)//这一点注意:对于偶数个结点,最后一次fast为NULL,如果不跳出,继续判断的话会出错
break;
}
return slow;
四:取链表倒数第K个结点
此题可以用朴素解法,也可以将链表导致,求其第k个结点。但更为好的方法还是双指针法
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{
struct ListNode* slow=pListHead;
struct ListNode* fast=pListHead;
while(k--)
{
if(fast)//注意有一种非常特殊的情况就是,链表有5个结点但是让你求倒数第8个结点,这种情况会出现内存错误
fast=fast->next;
else
return NULL;
}
while(fast!=NULL)
{
slow=slow->next;
fast=fast->next;
}
return slow;
}
五:合并链表
思路不难,创建新的链表,然后分别遍历两个原链表,把小的插入
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
struct ListNode* cur1=l1;
struct ListNode* cur2=l2;
struct ListNode* l3=(struct ListNode*)malloc(sizeof(struct ListNode));
l3=NULL;
struct ListNode* cur3=l3;
if(cur1==NULL && cur2!=NULL)//如果第一个空,第二个不空
{
return cur2;
}
if(cur1!=NULL && cur2==NULL)//如果第二个空,第一个不空
{
return cur1;
}
while(cur1!=NULL && cur2!=NULL)
{
if(cur2->val<cur1->val)
{
if(l3==NULL)//特殊处理头结点
{
l3=cur2;
cur3=l3;
cur2=cur2->next;
}
else
{
cur3->next=cur2;
cur2=cur2->next;
cur3=cur3->next;
}
}
else
{
if(l3==NULL)//特殊处理头结点
{
l3=cur1;
cur3=l3;
cur1=cur1->next;
}
else
{
cur3->next=cur1;
cur1=cur1->next;
cur3=cur3->next;
}
}
}
if(cur3==NULL)//l1和l2同时是空链表
{
return cur3;
}
if(cur1==NULL)//cur1等于NULL了说明,说明l2长于l1
{
cur3->next=cur2;
}
if(cur2==NULL)//cur2等于NULL了说明,说明l1长于l2
{
cur3->next=cur1;
}
return l3;
}
六:分割链表
此题要注意,要保持相对顺序不变,比如4->3->2->1,要把所有小于3的结点放在其余结点之前,那么可以是2->1->4->3,但是决不能是1->2->4->3或2->1->3->4等。
这个题类似于上述合并链表,选定题目要求的元素,遍历此链表,小于此元素的尾插到一个链表,大于的则尾插到另一个链表。但是尾插时会有一个问题,就是新创造的头指针开始为NULL,尾插时会出现NULL错误,所以为了避免这种问题,我们可以创造一个“哨兵”结点,该哨兵结点并不存储任何有效数据,但是它能使尾插的代码统一变为"head->next==Newhead"。
class Partition {
public:
ListNode* partition(ListNode* pHead, int x)
{
struct ListNode*small_tail=NULL;//小的尾节点
struct ListNode*small_guard=(ListNode*)malloc(sizeof(ListNode));//小的哨兵结点
small_guard->next=NULL;
small_tail=small_guard;
struct ListNode*large_tail=NULL;//大的尾节点
struct ListNode*large_guard=(ListNode*)malloc(sizeof(ListNode));//大的哨兵结点
large_guard->next=NULL;
large_tail=large_guard;
struct ListNode* cur=pHead;//遍历指针
while(cur)
{
if(cur->val<x)//小于x进行samll链表的尾插
{
small_tail->next=cur;
small_tail=small_tail->next;
cur=cur->next;
}
else//大于x进行large链表的尾插
{
large_tail->next=cur;
large_tail=large_tail->next;
cur=cur->next;
}
}
small_tail->next=large_guard->next;//小的后面连上的大的
large_tail->next=NULL;//注意收尾
return small_guard->next;
}
};
七:链表的回文结构
所谓回文数,就是指正读和反读一样的数字,比如1221。
题目中对于空间复杂度和时间复杂度都有限制,所以就不能使用建立数组的方式进行比较,即使没有这种限制,采用此方法也是不可取的,因为一旦链表过长,时间复杂度将会很大。
我们的思路是,利用之前讲过的双指针法找到中间节点,以中间节点为新的链表,对后半部分进行逆置操作,然后进行比较
这里有几点需要说明
ListNode* reverse(ListNode* head)//逆置
{
ListNode* newHead=NULL;
ListNode* cur=head;
while(cur)
{
head=cur;
cur=cur->next;
head->next=newHead;
newHead=head;
}
return newHead;
}
class PalindromeList{
public:
bool chkPalindrome(ListNode* A){
ListNode* slow=A;
ListNode* fast=A;
ListNode* pre=NULL;
while(fast->next)
{
pre=slow;
fast=fast->next->next;
slow=slow->next;
if(fast==NULL)
{
break;
}
}
pre->next=NULL;//断开
slow=reverse(slow);//逆置,此时slow指向“原链表最后一个”
while(A)//夹逼比较
{
if(A->val!=slow->val)//一旦出现不相等,一定不相等
{
return false;
}
else
{
A=A->next;
slow=slow->next;
}
}
return true;//要是可以执行到这里,那么肯定是回文结构
}
};
八:相交链表
此题最大的障碍就是,两个链表的长度可能不一致,这样一来就无法比较了。所以可以先分别求出两个链表的长度,接着计算出他们的差距,让长链表先走完这段差距,这样一样短的和长的就能同时开始向后走了,一旦出现某个位置相同,那么此位置必然是相交位置
注意下面对于长短链表的处理,如果硬要判断出A长还是B长,那么就要进行分类。所以可以让一个变量longlist始终保存最长的那个链表的地址(也就是先假设再调整)。
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
int length_A=0;
int length_B=0;
int gap=0;
struct ListNode* curA=headA;
struct ListNode* curB=headB;
while(curA)
{
++length_A;
curA=curA->next;
}
while(curB)
{
++length_B;
curB=curB->next;
}
struct ListNode* longlist=headA;//注意以下处理
struct ListNode* shortlist=headB;
if(length_B>length_A)
{
longlist=headB;
shortlist=headA;
}
gap=abs(length_A-length_B);
while(gap--)//先让长的走完差距
{
longlist=longlist->next;
}
while(longlist)
{
if(longlist==shortlist)
return longlist;
longlist=longlist->next;
shortlist=shortlist->next;
}
return NULL;
}
九:环状链表
可以用快慢指针,让快指针先走(每次2步),慢指针后走(每次1步),当两个指针都进入环内时,经过一定次数,一定有快慢指针相等,也就证明有环
也就是此题逻辑为,如果fast某一刻为NULL了,则一定没有环,但是如果有环,那么fast和slow一定能相遇,从而判断出来
这里还需要特别注意,快指针必须每次2步,因为如果其他步数,就会存在覆盖不全的情况,或者说相遇的几率大大减小,有可能仅仅只差一步,但是fast恰好越过slow,类似于死循环了。
这一点可以证明一下它们一定相遇
bool hasCycle(struct ListNode *head)
{
struct ListNode* slow=head;
struct ListNode* fast=head;
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
return true;
}
return false;
}
十:环状链表2
思路一
struct ListNode *detectCycle(struct ListNode *head)
{
if(head == NULL)//如果链表为空,直接返回空
return NULL;
struct ListNode* slow=head;
struct ListNode* fast=head;
struct ListNode* cur1=head;
struct ListNode* cur2=NULL;//cur1和cur2分别指向断开后的两个链表的头部
int lengthA=0;
int lengthB=0;
int gap=0;
while(fast && fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
break;//链表有环
}
}
if(fast==NULL)//做安全处理,防止fast=NULL,而执行fast=fast->NULL
return NULL;
fast=fast->next;//断开操作
slow->next=NULL;
cur2=fast;
while(cur1)//下面全是相交链表的代码
{
++lengthA;
cur1=cur1->next;
}
while(cur2)
{
++lengthB;
cur2=cur2->next;
}
struct ListNode* longlist=head;
struct ListNode* shortlist=fast;
if(lengthB>lengthA)
{
longlist=fast;
shortlist=head;
}
gap=abs(lengthA-lengthB);
while(gap--)
{
longlist=longlist->next;
}
while(longlist)
{
if(longlist==shortlist)
return longlist;
longlist=longlist->next;
shortlist=shortlist->next;
}
return NULL;
}
思路二
这种思路,需要进行证明,且不太好理解,但是代码十分简单
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* slow=head;
struct ListNode* fast=head;
while(fast && fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
break;
}
if(fast==NULL || fast->next==NULL)
return NULL;
struct ListNode* cur=head;
while(cur!=fast)
{
cur=cur->next;
fast=fast->next;
}
return cur;
}
十一:复杂链表复制
struct Node* copyRandomList(struct Node* head)
{
if(head==NULL)
{
return NULL;
}
struct Node* cur=head;
while(cur)//复制链表,复制一个就把它接到原来的后面,相当于新的链表和原来的链表错一位
{
struct Node* NewNode=(struct Node*)malloc(sizeof(struct Node));
NewNode->next=NULL;
NewNode->random=NULL;
NewNode->val=cur->val;
struct Node* next=cur->next;
cur->next=NewNode;
NewNode->next=next;
cur=next;
}
cur=head;
while(cur)//新链表结点的random等于,对应旧链表的结点random的下一个
{
struct Node* behind=cur->next;
if(cur->random!=NULL)
behind->random=cur->random->next;
else
behind->random=NULL;
cur=cur->next->next;
}
cur=head;
struct Node* returnhead=head->next;
while(cur)//把链表断开
{
struct Node* NewNode=cur->next;
struct Node* next=NewNode->next;
cur->next=next;
if(next==NULL)
{
NewNode->next=NULL;
break;
}
NewNode->next=next->next;
cur=next;
}
return returnhead;
}
十二:链表插入排序
将一个单链表进行插入排序
此题,可以使用一个sorthead保存头结点,然后剩余结点挨个插入,具体插入时注意以下细节
struct ListNode* insertionSortList(struct ListNode* head)
{
if(head==NULL)//如果传回来的是空链表,直接返回
{
return NULL;
}
struct ListNode*next=NULL;
struct ListNode* cur=NULL;
struct ListNode* sorthead=head;//使用sorthead保存head
head=head->next;从head的下一个结点开始逐个插入
sorthead->next=NULL;//注意这一点不要忘了,不然会陷入死循环
while(head)//head一直往后走,作用时检视每个待插入结点
{
if(head->val<sorthead->val)//第一种情况就是如果待插入的结点小于头结点,那么进行头插
{
next=head->next;
head->next=sorthead;
sorthead=head;//注意把头更新
head=next;
}
else//第二种情况是大于等于,此时就要在链表内进行逐个比较插入
{
cur=sorthead;//cur用于扫描sorthead这个链表
while(cur)
{
if(cur->next==NULL)//一个特殊情况——一旦扫描到最后一个结点,那么说明此时待插入的结点相较于sorthead中的所有结点来说是最大的,所有插入末尾即可
{
next=head->next;
cur->next=head;
cur=cur->next;
head=next;
cur->next=NULL;
break;//一旦插入就跳出循环,不然会死循环
}
if(head->val<(cur->next)->val)//这里必须使用(cur->next)->val,不能用cur->val,因为在插入时我们要插入到前面,而单链表只可以找到后面
{
next=head->next;
head->next=cur->next;
cur->next=head;
head=next;
break;//注意跳出
}
else
{
cur=cur->next;//如果不符合上述两种情况,那么cur向后走,继续比较
}
}
}
}
return sorthead;
}
十三:链表移除全部重复元素
此题思路不难,但是需要考虑的情况较多,容易写错
首先是正常情况1->2->3->3->4->4->5
链表由于其特殊性,所以出错的地方往往就是头或尾,而本题没有通过全部用例,也正是因为头尾这个特殊情况需要特殊处理
特殊情况一1->1->1->3->4
特殊情况2:1-2-3-3-3
最后:
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead==NULL || pHead->next==NULL)
{
return pHead;
}
ListNode* prev=NULL;
ListNode* cur=pHead;
ListNode* next=cur->next;
while(next)
{
if( cur->val != next->val)
{
prev=cur;
cur=next;
next=next->next;
}
else
{
while(next && cur->val==next->val)
{
next=next->next;
}
if(prev!=NULL)
{
prev->next=next;
}
else
{
pHead=next;
}
cur=next;
if(next)
next=cur->next;
}
}
return pHead;
}
};