双指针
1.找倒数第x个元素
查找一个单链表的倒数第x个元素,方法很多,这里引出一个极为常用的技巧
双指针
顾名思义,定义两个指针fast,slow

假设找倒数第3个元素,就让fast先走三个节点,再让fast和slow一同前进


当fast为空时,slow正好指向目标节点
对应函数:
int FindLNode(LNode*head, int x){
//定义双指针
LNode*fast = head;
LNode*slow = head;
//fast先走
while(x--){
fast = fast->next;
}
//移动fast和slow
while(fast){
fast = fast->next;
slow = slow->next;
}
//返回该元素
return slow->data;
}
2.找两个链表的相交节点
题目

输出c1节点的数据

以该示例为例
暴力循环也可以,但时间复杂度太高,可以思考能否用双指针这一技巧
还记得上一题的双指针是怎么玩的吗,先让fast走几步,再让fast和slow一起走
这题也一样,让pb指针先走一步,这样pa,pb就对齐了
接下来挨个比较节点是否相同就行(比较的不是节点元素,而是节点地址,即pa和pb是否相同)
总结下来三个步骤:
1.算出快指针要走的步数(A,B长度之差)
2.移动快指针,使两指针对齐
3.遍历比较(比较的是pa和pb)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
//第一步,算A,B长度差
int lenA = 0;
int lenB = 0;
struct ListNode*pa = headA;
struct ListNode*pb = headB;
while(pa->next){
pa = pa->next;
lenA++;
}
while(pb->next){
pb = pb->next;
lenB++;
}
int n = lenA - lenB;//n是快指针要走的步数
if(n<0) n*=-1; //n得是正数
//第二步移动快指针
pa = headA;
pb = headB;
if(lenA > lenB){ //得判断一下pa,pb谁是快指针
while(n--){
pa = pa->next;
}
}else{
while(n--){
pb = pb->next;
}
}
//第三步遍历比较
while(pa!=pb){ //出循环后pa,pb要么是同一个节点,要么都是NULL
pa = pa->next;
pb = pb->next;
}
return pa;
}
能力有限,写的不是很简洁,思路还算清晰,大家可以尝试自行编写优化,到leecode上测试
3.将一个链表从中间分开成两个链表
要求第一个链表长度>=第二个链表长度
定义两个指针,一快一慢

在循环中,快指针每次走两步,慢指针每次走一步
当节点数是偶数时,最后的情况

当节点数时奇数时,最后的情况

由上图可见,循环条件应该是:fast存在 并且 fast->next存在
给出模板,用head1和head2作为头指针
#include<stdio.h>
#include<stdlib.h>
typedef struct node{
int data;
struct node*next;
}LNode;
LNode* InitLNode(){
LNode*head = (LNode*)malloc(sizeof(LNode));
head->next=NULL;
return head;
}
void CreateLNode(LNode* head, int n){
LNode*last = head;
while(n--){
LNode*p = (LNode*)malloc(sizeof(LNode));
p->next = NULL;
scanf("%d", &p->data);
last->next = p;
last = last->next;
}
}
void FreeLNode(LNode* head){
LNode*p=head;
while(p){
LNode*r=p;
p=p->next;
free(r);
}
}
void Solve(LNode*head, LNode*&head1, LNode*&head2){
/**************************************/
/**************************************/
}
void Show(LNode*head1, LNode*head2){
LNode*p=head1;
if(!p) printf("空");
while(p){
printf("%d ", p->data);
p=p->next;
}
printf("\n");
p=head2;
if(!p) printf("空");
while(p){
printf("%d ", p->data);
p=p->next;
}
}
int main()
{
LNode* head = InitLNode();
int n=0;
scanf("%d", &n);
CreateLNode(head, n);
LNode*head1 = NULL;
LNode*head2 = NULL;
Solve(head, head1, head2);
Show(head1, head2);
FreeLNode(head);
return 0;
}
关键函数
void Solve(LNode*head, LNode*&head1, LNode*&head2){
//排除特殊情况
if(!head) return;
if(!head->next) return;
//中间分开
LNode*fast = head;
LNode*slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
}
head1 = head->next;
head2 = slow->next;
slow->next = NULL;
}
数组标记法/哈希标记法
1.

(题目中两个n不一样)
解法:
假设所有数据都<=n
那么定义一个长度为n+1的数组(下标从0到n),初始所有元素为0
然后遍历链表,将每个节点数据的绝对值作为下标,访问数组元素
如果为0,说明这个数据第一次出现,再把0改1
如果为1,说明这个数据已出现过,删去该节点


给出题目模板
#include<stdio.h>
#include<stdlib.h>
typedef struct node{
int data;
struct node*next;
}LNode;
LNode* InitLNode(){
LNode*head = (LNode*)malloc(sizeof(LNode));
head->next=NULL;
return head;
}
void InsertLNode(LNode*head, int x){
LNode*p = (LNode*)malloc(sizeof(LNode));
p->next = NULL;
p->data = x;
p->next = head->next;
head->next = p;
}
void FreeLNode(LNode* head){ //依次释放每个节点的内存空间
LNode*p=head; //p指针用来遍历链表
while(p){
LNode*r=p; //r指针辅助删除节点
p=p->next;
free(r);
}
}
void Solve(LNode*head, int x, int*arr){
/*****************************************/
/*****************************************/
}
void ShowLNode(LNode*head){
LNode*p=head->next;
while(p){
printf("%d ", p->data);
p=p->next;
}
}
int main()
{
LNode* head = InitLNode();
InsertLNode(head, 15);
InsertLNode(head, -7);
InsertLNode(head, -15);
InsertLNode(head, -15);
InsertLNode(head, 21);
int n = 21;//上界
int arr[n+1] = {0}; //数组初始化为0
Solve(head, n, arr);
ShowLNode(head);
FreeLNode(head); //该函数用于释放内存
return 0;
}
注:可以用abs(x)表示x的绝对值,但要包含头文件stdlib.h
答案
void Solve(LNode*head, int x, int*arr){
LNode*p = head->next;//p指针遍历链表
LNode*fp = head; //fp指针指向p前面的节点,这样便于删除操作
while(p){
if(arr[abs(p->data)] == 1){
//删除节点
LNode*r = p; //待删除节点
p = p->next; //先把p移动到下一个节点
fp->next = p;//连接起来
free(r); //删除
}else{
//下一个
arr[abs(p->data)] = 1;
p = p->next;
fp = fp->next;
}
}
}
三指针
常用于反转链表

head是指向第一个节点的指针,这个链表没用空头节点,如下图

第一步,先修改head所指节点,head->next=pre (pre初始是NULL)

第二步,移动三个指针

将这两步一直循环下去
1

2

1

2

1

2

1

最后一轮循环做到第一步就结束了
这是的head就是新的头节点
leecode代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head){
if(!head || !head->next) return head;
struct ListNode*pre = NULL;
struct ListNode*nxt = head->next;
while(head){
//修改head所指节点
head->next = pre;
//移动三个节点
if(!nxt) break;//最后一轮循环才会执行
pre = head;
head = nxt;
nxt = nxt->next;
}
return head;
}
例题1

思路:
1.把链表从中间分开


2.把后半链表反转

3.逐个插入组合

给出模板
#include<stdio.h>
#include<stdlib.h>
typedef struct node{
int data;
struct node*next;
}LNode;
LNode* InitLNode(){ //LNode*类型的函数,返回值为头指针head
LNode*head = (LNode*)malloc(sizeof(LNode));//为头指针分配大小为一个结点的变量空间
head->next=NULL; //别忘了定义next指针,头节点的data一般不用管
return head; //返回我们定义的头指针
}
void CreateLNode(LNode* head, int n){//尾插法
LNode*last = head; //新建尾指针
while(n--){
LNode*p = (LNode*)malloc(sizeof(LNode));//新建节点
p->next = NULL;
scanf("%d", &p->data); //写数据
last->next = p; //连接
last = last->next; //更新尾指针
}
}
void FreeLNode(LNode* head){ //依次释放每个节点的内存空间
LNode*p=head; //p指针用来遍历链表
while(p){
LNode*r=p; //r指针辅助删除节点
p=p->next;
free(r);
}
}
void Solve(LNode*head){
/********************************/
/********************************/
}
void Show(LNode*head){
LNode*p=head->next;
while(p){
printf("%d ", p->data);
p=p->next;
}
}
int main()
{
LNode* head = InitLNode();
int n=0;
scanf("%d", &n);
CreateLNode(head, n);
Solve(head);
Show(head);
FreeLNode(head); //该函数用于释放内存
return 0;
}
关键函数
void Solve(LNode*head){
if(!head) return;
if(!head->next) return;
//中间分开
LNode*fast = head;
LNode*slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
}
LNode*newhead = slow->next;
slow->next = NULL;
//后半反转
LNode*pre=NULL;
while(newhead){
LNode*nex=newhead->next;
newhead->next=pre;
pre=newhead;
newhead=nex;
}
newhead = pre;
//见缝插针
LNode*p1=head->next;
LNode*p2=newhead;
while(p2){
LNode*p3=p2->next;
p2->next = p1->next;
p1->next = p2;
p1=p2->next;
p2=p3;
}
}
例题2
leetcode习题

经典例题
此题要返回环的首个节点,故分为两步
1.判断是否有环

快慢指针,fast每次走两步,slow每次走一步,若两指针能重合,环存在

这里重点要记住while循环的条件fast && fast->next
2.找环的首节点
当fast和slow重合时,此重合节点到环首节点,与head节点到环首节点距离相等
struct ListNode *detectCycle(struct ListNode *head) {
//排除无节点,单节点情况
if(!head || !head->next) return NULL;
//判断是否有环
struct ListNode*fast = head;
struct ListNode*slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
if(fast == slow) break;
}
if(fast != slow) return NULL; //无环
//找环的起点
struct ListNode*p = head;
while(p!=fast){
p=p->next;
fast = fast->next;
}
return p;
}
可到leecode上测试

5522

被折叠的 条评论
为什么被折叠?



