✨✨✨专栏:数据结构
🧑🎓个人主页:SWsunlight
一、 OJ题 返回倒数第K个节点:
1、遍历链表一遍:用2个指针,phead和ptail先让ptail先走k步,然后让2个指针一起走,快的走到NULL即可
就是数学问题:第n-k个就是倒数第k个
2、也可以遍历一遍算出节点个数n,然后倒数第k个就是节点n-k,在遍历一次即可
代码:
int kthToLast(struct ListNode* head, int k){ struct ListNode*phead=head; struct ListNode*ptail=head; //先让快的走k步 while(k--) { ptail=ptail->next; } while(ptail) { ptail=ptail->next; phead=phead->next; } return phead->val; }
falst 和 slow
二、OJ题 合并2个有序链表:
注意:题目说的链表是有序的,那么我们可以建立一个哨兵位 然后将各个节点放到哨兵位的后面 进行有序排序即可:
//改名,便于使用 typedef struct ListNode ListNode; struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) { //先判空: if(list1==NULL) return list2; if(list2==NULL) return list1; ListNode*head; ListNode*tail; ListNode*l1 = list1; ListNode*l2 = list2; head = tail = (ListNode*)malloc(sizeof(ListNode)); //只要有一个为NULL就出来; while(l1&&l2) { //小的先放入后面 if(l1->val<l2->val) { tail->next = l1; tail=tail->next; l1 = l1->next; } else { tail->next = l2; tail = tail->next; l2 = l2->next; } } //出循环2种情况:l1和l2都为NULL // l1或者l2 一个为NULL] //不要用循环了,因为其中一个为NULL,tail后面直接就是加上了另一个链表的剩下的节点 if(l1) { tail->next = l1; tail = tail->next; } if(l2) { tail->next = l2; tail = tail->next; } ListNode*ret = head->next; //申请的空间还给操作系统 free(head); head = NULL; return ret; }
三、相交链表:
关于这个题,可以先考虑是否相交遍历(同时记录2个指针的长度)对最后一个节点地址进行判断是否相等(相等就是说明这个环相交),若是不相交直接结束程序了;
若是相交,我们就得继续考虑:发现了么,若是相交,看头节点的位置,2个链表会有一个距离差,这个时候我们可以长的链表先走完距离差,然后2个链表一起遍历,同速度一定相遇
注意:我们用地址那判断,不用节点里的数据是因为无法保证后前面数据有相同的,地址更保险
typedef struct ListNode ListNode; struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { ListNode*l1=headA; ListNode*l2=headB; //记录节点长度 int k = 0; int n = 0; //l1、l2的位置不能到NULL,不然这不就一定相等了(链表的最后一个节点指向的下一个地址为NULL) while(l1&&l1->next) { l1 = l1->next; k++; } while(l2&&l2->next) { l2 = l2->next; n++; } //若是最后一个节点不相等 说明不为环 if(l1!=l2) { return NULL; } //为环继续进行; ListNode* phead1 = headA; ListNode* phead2 = headB; //假设法:1放长的,2放小的链表 if(k<n) { phead1 = headB; phead2 = headA; } //求2个的链表的长度差 //abc为绝对值函数,返回绝对值 int key = abs(n-k); while(key--) { phead1=phead1->next; } //同时遍历,相同时结束 while(phead1!=phead2) { phead1 = phead1->next; phead2 = phead2->next; } return phead1; }
用到了假设法,还有 绝对值函数:abs()函数
四、链表的中间节点:
快慢指针即可,快的指针到到尾节点时,慢指针刚好走了一半;不要考虑节点个数为奇数还是偶数,因为题目说了若是2个中间节点返回第2个,如下:当快指针结束会有2种情况,刚好跟节点个数有关 记住节点个数:n 中间节点的公式: n / 2 表示从第一个节点的下一个节点开始数,第几个节点就是中间节点
typedef struct ListNode ListNode; struct ListNode* middleNode(struct ListNode* head) { ListNode* fast = head; ListNode* slow = head; while(fast&&fast->next) { fast = fast->next->next; slow = slow->next; } return slow; }
五、反转链表:
看到题目可能会想到创建一个新链表,将原链表的节点以头插的形式插入到新链表中,但是我今天想用的是不创建新的链表,使用迭代的方式进行实现反转
将l1当做尾节点依次类推
用到了3个指针 l3指向NULL,l2用来保存下个节点的地址,l1用来修改所指向节点的next的地址
移动步骤如下:1、修改l1的next指向l3
2、将l3移动到l1的位置
3、将l1移动到l2的位置
4、将l2后移一位
结束条件 我们将l2作为条件,当l2指向空时结束,如下:但是l1的next是还没有变的,使用if语句判断 进行修改 即可,最后返回 l1 刚好是头节点的地址
typedef struct ListNode ListNode; struct ListNode* reverseList(struct ListNode* head) { //判NULL,若是为空则直接返回 if(head==NULL) { return head; } //l2保存下一个节点地址,l1修改其所在节点的next ListNode*l1 = head; ListNode*l2 = head->next; //存储l1->next的节点,开始时是NULL,因为头节点变成尾节点的next指向的内容为NULL ListNode*l3 = NULL; while(l2) { l1->next = l3; l3 = l1; l1 = l2; l2 = l2->next; //当l2NULL;l1在尾节点,直接修改尾节点的next内容, if(l2==NULL) { l1->next = l3; } } return l1; }
注意 :要判空,不然l2赋值时就非法操作了(对空指针进行解引用)
六、链表的回文结构:
快慢指针法:
看到回文结构,我们可以用到中间节点和反转链表2个方法结合,CV一下;我们将链表的中间节点后面的节点进行反转,然后进行判断
这里取的是第3个节点进行反转,但是因为我么的第2个节点next指针指向的是第三个,我们没有对它修改,所以拆开以后就是如下的样子:
无论奇数还是偶数都没有影响
typedef struct ListNode ListNode; class PalindromeList { public: struct ListNode* middleNode(struct ListNode* head) { ListNode* fast = head; ListNode* slow = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; } return slow; } struct ListNode* reverseList(struct ListNode* head) { //判NULL,若是为空则直接返回 if (head == NULL) { return head; } //l2保存下一个节点地址,l1修改其所在节点的next ListNode* l1 = head; ListNode* l2 = head->next; //存储l1->next的节点,开始时是NULL,因为头节点变成尾节点的next指向的内容为NULL ListNode* l3 = NULL; while (l2) { l1->next = l3; l3 = l1; l1 = l2; l2 = l2->next; //当l2NULL;l1在尾节点,直接修改尾节点的next内容, if (l2 == NULL) { l1->next = l3; } } return l1; } bool chkPalindrome(ListNode* A) { //pet接受后半部分的反转链表 ListNode* pet = middleNode(A); ListNode*A1 =A; ListNode*A2 =reverseList(pet); //进行比较 while(A2) { if(A2->val!=A1->val) { return false; } A2=A2->next; A1=A1->next; } return true; } };
我在牛客网写的这个题目,因为c++是兼容c的牛客题没有c语言这个环境的选项,就直接在c++里面写了
七、移除链表元素:
3个指针 :
将节点取出来创建成一个“新”的链表(一个记录头一个记录尾)再来一个遍历链表,遍历到NULL时结束循环;以上面题目提供的例子为例的话,当我将所有节点取出重新组合后,为下图第一个图形,发现会有一个6没去掉,因为节点指向下一个节点的next没有修改,所以最后循环结束应该要加一个判断条件 尾指针是否为NULL,不是NULL,就给它next赋值NULL;
typedef struct ListNode ListNode; struct ListNode* removeElements(struct ListNode* head, int val) { //先判空 if(head==NULL) { return head; } //记录头结点 ListNode* phead= NULL; //记录尾节点 ListNode*ptail = NULL; //用来遍历: ListNode*pcur=head; while(pcur) { if(pcur->val!=val) { //是否为空链表 if(phead==NULL) { phead= ptail=pcur; } else { ptail->next=pcur; ptail = ptail->next; } } pcur = pcur->next; } //出循环以后,若是ptail不是空,将下一个地址给空 if(ptail) { ptail->next=NULL; } return phead; }
八、随机链表的复制:
错位插入,复制每个节点,然后和原链表以如下方式连接起来,形成一个新的链表
如下:要向堆区申请空间,要创建一个节点
给random分配
typedef struct Node Node; struct Node* copyRandomList(struct Node* head) { //创立一个新的节点 Node*phead = head; //phead为NULL时结束 while(phead) { //申请节点,创建新的节点,需要申请空间 Node*node = (Node*)malloc(sizeof(Node)); node->val = phead->val; //我的节点next为head的下一个节点地址 node->next=phead->next; //初始情况: node->random = NULL; //phead的next指向node phead->next=node; phead = node->next; } //对上面的部分random进行分配 Node* cal = head; while(cal) { //cal的下一个节点就是要分配的节点 Node* pov = cal->next; if(cal->random==NULL) { pov->random=NULL; } else { pov->random = cal->random->next; } cal = pov->next; } //拆开 //裁掉 cal = head; //尾 Node* povhead=NULL; //头不动 Node* nude = NULL; while(cal) { //判空 if(povhead==NULL) { povhead = nude = cal->next; } else{ povhead->next = cal->next; povhead = povhead->next; } //恢复原链表 cal->next = povhead->next; cal = cal->next; } return nude; }