文章目录
前言
如果可以自己做出了这些链表oj题,那么说明链表基础掌握的就还行,当然,一开始不会的话也没什么,我相信学完这些题会进一步加深对链表的理解和一些技巧;
一、移除链表元素
把节点的值和val相等的节点释放掉,再组成新的链表;
这个时候为了方便头指针的释放,我们链表带头,因为不用考虑头是否要释放;然后再用两个指针,一前一后走,如果值不相等,就迭代走下去,代码是:tail=cur,cur=cur->next;,但是开始的时要,phead->next=head,tail=head;
下面代码我就多了些操作,但是思路都作业,要是值相等,那么先保存该节点的下一个节点,然后free该节点,把前面一个节点和下一个节点连接起来;
next=cur->next,free(cur),tail->next=cur=next;
最后值得注意的是:如果最后一个或者几个都是相等的节点,那么全部free完后,最后一个不相等的节点的next还存着free掉的节点地址,所有出循环后要把最后一个节点的next赋空指针;然后就是把头free了,返回头的下一个节点;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val){
if(head==NULL)
return head;
struct ListNode* phead=(struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* tail=phead,*cur=head;
while(cur){
if(cur->val!=val){
tail->next=cur;
tail=tail->next;
cur=cur->next;
}
else{
struct ListNode* next=cur->next;
free(cur);
cur=next;
}
}
if(tail)
tail->next=NULL;
head=phead->next;
free(phead);
phead=NULL;
return head;
}
二 . 反转链表
我们用三个指针,后面两个指针用于反转两个节点,最前面的指针用于保存下一个节点,便于迭代;
这样就走起来了,prev指针在最左边,也就是后面,cur是当前,next是最前面的指针,走的时候是cur->next=prev;prev=cur;cur=next;而next则是一直向后的,所有就这样走起来了;不清楚的可以画一下图,更直观;
struct ListNode* reverseList(struct ListNode* head){
if(head==NULL)
return head;
struct ListNode* prev=NULL,*cur=head,*next=cur;
while(next){
next=cur->next;
cur->next=prev;
prev=cur;
cur=next;
}
return prev;
}
三 . 链表的中间节点
这里有奇数链表和偶数链表,奇数返回的奇数中间的节点,偶数就是第二个中间节点嘛;然后这里介绍一个方法叫做快慢指针,也许听过,这个方法用来解决这题非常简单,快指针走两步,慢指针走一步,当链表为奇数时,快指针走到fast->next==NULL的时候,慢指针就是停在了中间节点;当链表为偶数时,fast指针刚好走到空的时候,慢指针就是第二个中间节点了;
struct ListNode* middleNode(struct ListNode* head){
struct ListNode* fast=head,*slow=head;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
四. 链表中倒数第k个结点
先看正常情况,思路是让一个指针先走k步,然后再和头部指针一起走,当前面指针走到空时,后面的指针指向的就是倒数第K个节点了;
当k=0的时候,这个思路没问题;
当k=链表长度的时候,前面的指针直接走到空了,后面的指针还在第一个节点,没问题;
当k>链表长度的时候,这个时候就会有问题了,前面的指针会走到空了还在走,这是不对的,所以我们要让前面走k步的循环里面指针进来的里面不可以是空指针,因为空指针不可能在向后走了,直接返回空指针;
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
// write code here
struct ListNode* fast=pListHead,*slow=pListHead;
while(k--){
if(fast==NULL)
return NULL;
fast=fast->next;
}
while(fast){
fast=fast->next;
slow=slow->next;
}
return slow;
}
五. 合并两个有序链表
还是为了方便,带个头节点,有的题带头节点是会方便很多的,我们考虑问题的时候要有加头节点的考虑哦;把两个链表中小的值的节点尾插到我们自己的链表后面,就这样一直尾插,到一个链表都尾插完后跳出循环,然后把没有插完的链表直接连接就行了;
但是有可能两个或者其中一个链表为空,我的话单独处理;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if(list1==NULL&&list2==NULL)
return NULL;
if(list1==NULL)
return list2;
if(list2==NULL)
return list1;
struct ListNode* phead=(struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* tail=phead,*head1=list1,*head2=list2;
while(head1&&head2){
if(head1->val<head2->val){
tail->next=head1;
tail=tail->next;
head1=head1->next;
}
else{
tail->next=head2;
tail=tail->next;
head2=head2->next;
}
}
if(head1)
tail->next=head1;
if(head2)
tail->next=head2;
struct ListNode* head=phead->next;
free(phead);
phead=NULL;
return head;
}
六. 链表分割
我们可以把小于x的节点组成一个链表,然后大于x的节点组成一个链表,然后把大于的链表连接到小于的链表后面就行了;不够值得注意的是,我们有要带头,把两个链表都带头,不过那么可以不带头试试,的确会更麻烦;
还有要注意的是,链表都大于等于或者小于等于x的情况,这个时候我们的方法可以解决,不过就是把头的下一个节点置空一下就行了;还有就是大的链表的尾节点不一定是原链表的尾节点,它的next不一定指向空,所以我们需要自己把那个节点置空;
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
// write code here
//分成两个链表,一个所有值小于x的链表,另一个大于或者等于x的值的链表
struct ListNode* pheadsmall=(struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* pheadgreater=(struct ListNode*)malloc(sizeof(struct ListNode));
pheadsmall->next=NULL;//带头方便头节点问题
pheadgreater->next=NULL;
struct ListNode* tail=pHead,*smalltail=pheadsmall,*greatertail=pheadgreater;
while(tail){
if(tail->val<x){
smalltail->next=tail;
smalltail=smalltail->next;
tail=tail->next;
}
else{
greatertail->next=tail;
greatertail=greatertail->next;
tail=tail->next;
}
}
smalltail->next=pheadgreater->next;
greatertail->next=NULL;//大的链表尾节点的next不一定是空指针;
struct ListNode* head=pheadsmall->next;
free(pheadsmall);
free(pheadgreater);
pheadgreater=NULL;
pheadsmall=NULL;
return head;
}
};
七. 链表的回文结构
两个思路,第一个就是把原链表复制一个,把复制链表反转一下,再于原链表比较,要是一样,就没错,要是有一个节点不对,那么就不是回文结构;
第二个方法就是把把链表分成两条,前面的节点一条,中间节点后面的一条链表(包括中间节点),把后面的一条链表反转,再和前面链表对比,直到有链表到空,就是回文链表,要是有一个对不上,就不是;
还有要注意的是,第一个思路比较多人想到的,第二个思路不好想;我就写一下第二个方法的代码;
class PalindromeList {
public:
struct ListNode* Getmid(struct ListNode* phead){
struct ListNode* fast=phead,*slow=phead;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
struct ListNode* Turnslist(struct ListNode* phead){
struct ListNode* n1=NULL,*n2=phead,*n3=phead;
while(n2){
n3=n3->next;
n2->next=n1;
n1=n2;
n2=n3;
}
return n1;
}
bool chkPalindrome(ListNode* A) {
// write code here
struct ListNode* phead1=Getmid(A);
struct ListNode* phead2=Turnslist(phead1);
while(A&&phead2){
if(A->val!=phead2->val)
return false;
A=A->next;
phead2=phead2->next;
}
return true;
}
};
八. 相交链表
思路:要是相交,总有一个节点的地址相同,只要判断最后一个节点是否相同,那么就可以判断链表是否相交;要是相交,怎么判断第一个交点呢?
我们可以得出两条链表长度,然后长链表先走差值步数,然后两个链表指针一起走,直到指针相等时就是第一个节点指针了;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(headB==NULL)
return NULL;
if(headA==NULL)
return NULL;
struct ListNode* tail1=headA,*tail2=headB;
int l1=1,l2=1;
while(tail1->next){
++l1;
tail1=tail1->next;
}
while(tail2->next){
++l2;
tail2=tail2->next;
}
if(tail1!=tail2)//两个链表最后一个节点不相等,那么肯定不相交
return NULL;
struct ListNode* longslist=headA,*shortslist=headB;
if(l1<l2)
{
longslist=headB;
shortslist=headA;
}
//让长的链表先走差的步数
int n=abs(l1-l2);
while(n--){
longslist=longslist->next;
}
while(longslist!=shortslist){
longslist=longslist->next;
shortslist=shortslist->next;
}
return longslist;
}
九. 环形链表
判断是否带环,直接快慢指针解决,要是快慢指针相遇,那么肯定有环,要是循环可以结束,那么就不带环;
bool hasCycle(struct ListNode *head) {
struct ListNode* fast=head,*slow=head;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
return true;
}
return false;
}
带环问题还有延伸的问题:快指针走两步,慢指针走一步,是否一定可以追上?要是快指针走三步,慢指针走一步,结果当如何,会相遇不?快指针走四步,慢指针 一步呢,结果又是如何?当快指针走n步,慢指针走m步,结果又如何?
当fast走三步,slow走一步的情况:
其它情况和上面类似,都可以解释;
十. 环形链表 ||
两种方法;第一种:公式法,快慢指针的交点和起点一起开始走,都走一步,当相遇时,就是环的人口点;会证明;
第二种方法:快慢指针相遇节点的下一个节点为第二条链表的起节点,原链表起点就是第一条链表起节点,把快慢指针相遇节点的next置空,然后两条链表的第一个相交节点不就是环的人口节点吗;然后看情况要不要恢复链表,不能通过就恢复链表;
//第一种方法:
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* fast=head,*slow=head;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
if(slow==fast){//如果有环,可以根据公式得出人口
//快慢指针相遇节点开始和头节点一起走,相遇点就是答案
struct ListNode* cur1=head,*cur2=slow;
while(cur1!=cur2){
cur1=cur1->next;
cur2=cur2->next;
}
return cur1;
}
}
return NULL;//如果出来了循环,说明不带环
}
//第二种方法:
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(headB==NULL)
return NULL;
if(headA==NULL)
return NULL;
struct ListNode* tail1=headA,*tail2=headB;
int l1=1,l2=1;
while(tail1->next){
++l1;
tail1=tail1->next;
}
while(tail2->next){
++l2;
tail2=tail2->next;
}
if(tail1!=tail2)//两个链表最后一个节点不相等,那么肯定不相交
return NULL;
struct ListNode* longslist=headA,*shortslist=headB;
if(l1<l2)
{
longslist=headB;
shortslist=headA;
}
//让长的链表先走差的步数
int n=abs(l1-l2);
while(n--){
longslist=longslist->next;
}
while(longslist!=shortslist){
longslist=longslist->next;
shortslist=shortslist->next;
}
return longslist;
}
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* fast=head,*slow=head;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
if(slow==fast){//如果有环,可以将快慢指针相等的节点的next设置为空指针,然后本来的下一个节点变成第二个链表的头节点,这两个链表的交节点
//即是答案
struct ListNode* cur1=head,*cur2=fast->next;
fast->next=NULL;
struct ListNode* dst=getIntersectionNode(cur1,cur2);
return dst;
}
}
return NULL;//如果出来了循环,说明不带环
}