双指针技巧

本文介绍了双指针技巧,包括快慢指针在判断链表环和单链表是否为循环链表的应用,以及左右指针在有序数组中查找特定和的高效算法,如二分查找的简化版。这些技巧优化了解决相关问题的时间复杂度。
摘要由CSDN通过智能技术生成

常见的双指针技巧分为两类,一类是快慢指针,另一类是左右指针。

所谓快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动2,慢指针每次向前移动1次。一般用来解决链表中的问题。例如判定链表中是否有环,单链表是否为循环链表等等。

左右指针(对撞指针)呢,用于在已排序数组中找到两个数使其和为特定值,在字符串中判断是否回文,主要用来解决数组、字符串中的问题。例如二分查找等。

快慢指针的常见算法

快慢指针一般都初始化指向链表的头节点head,前进时快指针fast在前,慢指针slow在后。

循环链表

循环链表是一种链式存储结构,它的最后一个结点指向头结点,形成一个环。

循环链表的特点
//循环链表最后一个节点的指针指向第一个节点,形成闭环。

//可以通过任意节点进行遍历,每个节点都有下一个节点的指针。

//在遍历时可以无限循环下去,不会出现尾部节点的空指针错误。

//可以从任意节点开始遍历,选择一个合适的起始节点可提高操作效率。

//不增加额外存储花销
判断单链表是否为循环链表
typedef struct node
{
 
   int data;
   struct node *next;
}Node,*pnode;

int is_Clink( pnode p)
 {
     pnode fastpoint,slowpoint;
    fastpoint=p;
    slowpoint = p;
    while(slowpoint &&fastpoint)
    	{
    	 
	     slowpoint=slowpoint->next;
	     fastpoint=fastpoint->next->next;
	   if (slowpoint==fastpoint||fastpoint->next==slowpoint)
	   	printf("The si Clink\n");
	        return 0;
	   
    	}
	printf("This is linklist\n");
	return 1
 }
环形链表
环形链表的概念

在解答这个问题之前,我们先要弄清楚一个概念,什么是环形链表。

环形链表是一种特殊类型的链表数据结构,其最后一个节点的"下一个"指针指向链表中的某个节点,形成一个闭环。换句话说,链表的最后一个节点连接到了链表中的某个中间节点,而不是通常情况下连接到空指针(null)。如图:

bool hasCycle(struct ListNode *head) {
    struct ListNode*slow=head,*fast=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
        {
            return true;
        }
    }
    return false;
}

eg:

由于链表最后一个节点的下一个指针没有指向NULL(空指针),而是指向前面的某一个节点,所以我们不能再用 “current->next==NULL” 作为判断条件来遍历链表(会造成死循环),这时候就需要用到我们的快慢指针了。

判断链表是否有环

首先先说一下做这道题的思路,大体上类似于高中学习过的追及相遇问题

1.首先,让快慢指针fast、slow指向链表的头节点head

2.根据快慢指针的定义,快指针每次移动两个节点,慢指针移动一个节点

3.判断fast和slow是否移动到同一节点上,如果移动到同一节点上,就返回true,否则返回false

4.以上步骤会循环进行,直至fast或fast->next指向null(当链表中有环存在时,fast和fast->next永远不会指向null当一个链表中没有环时,fast一定会移动到链表的结尾;又因为fast一次移动两个节点,所以有两种情况:①fast移动两次后,刚好指向NULL,结束循环;②fast移动一次后就已经指向NULL,此时再进行移动,就会出现对NULL的解引用

struct ListNode* fast, *slow;
    fast=slow=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow=fast)
          return true;
    }
    return false;

左右(对撞)指针的常见算法

两个指针,一个位于头,一个位于尾,它们向中间移动(主要应用于有序数组

举个在力扣中做到的例子,找相应数字求和等于一个特定的值【题目:给定一个有序数组(数组是递增的),如数组arr = {1,4,5,7,9};找两个数之和为12,找到一组即可停止。】

如果用暴力求解法,那么就是for循环的两次遍历,这时的时间复杂度为O(n²)

代码如下

void find(int *a,int n,int temp)
{
	for(int i=0;i<n;i++)
	{
		for(int j=i;j<n;j++)
		{
			if(a[i]+a[j]==temp)
			{
				printf("%d+%d=%d\n",a[i],a[j],temp);
			}
		}
	}
}

这时就需要用到对撞指针了,那么显而易见会有三种情况:

(1)a[i]+a[j]<temp,我们则可知这两个指针对应的数组元素的和小于特值,但此时a[j]已经是数组里最大的元素了,所以使i++,对应下个更大的a[i]元素

(2)a[i]+a[j]>temp,同理,a[i]已经是数组里最小的元素了,两数之和还要比temp大,就只能让j--,从右向左寻找依次寻找更小的数,所以j--,对应下个更小的a[j]元素

(3)a[i]+a[j]==temp,满足条件,跳出循环

那么显然一次判断是出不了结果的,所以我们要用到循环,让循环跳出的条件是:左指针下标大于右指针下标,这时整个数组都已经在程序中筛查过一遍了,如果还是没有的话那么就不可能再有满足的元素了。

那么此时的代码就变成了:(这时的时间复杂度是O(N))

void find(int *a,int n,int temp,int i,int j)//i首,j尾
{
	while(i<j)
	{
		if(a[i]+a[j]<temp)
		{
			i++;
		}
		else if(a[i]+a[j]>temp)
		{
			j--;
		}
		else
		{
			printf("%d+%d=%d\n",a[i],a[j],temp);
			break;
		}
	}
}
二分查找

对撞指针的思路类似于二分查找

那么什么是二分查找呢,首先我们要知道二分查找的逻辑条件:①用于查找的数据是有序的查找的数量只能是一个

同时一定要注意查找的区间问题,因为查找的区间是不断迭代的,所以确定查找的范围十分重要,主要就是左右区间的开和闭的问题,开闭不一样,对应的迭代方式也不一样,主要分两种:

一、左闭右闭[left, right]

二、左闭右开[left, right)

思路大概就是这样,具体操作呢会在下一篇文章中总结。

  • 16
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值