数据结构-线性表-链表技巧

双指针

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.找两个链表的相交节点

题目

LCR 023. 相交链表 - 力扣(LeetCode)

输出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上测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值